From be22ef04693f1bb31b28233e6ad46a5bb6f95a51 Mon Sep 17 00:00:00 2001 From: yurixander <101931215+yurixander@users.noreply.github.com> Date: Sun, 4 Aug 2024 17:34:57 -0400 Subject: [PATCH 01/60] refactor(tangle-dapp): Rename things to accommodate for EVM LS --- .../stakeAndUnstake/ChainLogo.tsx | 23 ++-- .../ExchangeRateDetailItem.tsx | 4 +- .../stakeAndUnstake/LiquidStakeCard.tsx | 8 +- .../stakeAndUnstake/LiquidStakingInput.tsx | 35 +++--- .../LiquidStakingTokenItem.tsx | 8 +- .../stakeAndUnstake/LiquidUnstakeCard.tsx | 12 +- .../MintAndRedeemFeeDetailItem.tsx | 4 +- .../ParachainWalletBalance.tsx | 4 +- .../stakeAndUnstake/SelectTokenModal.tsx | 4 +- .../stakeAndUnstake/TokenChip.tsx | 4 +- .../UnstakeRequestsTable.tsx | 4 +- .../useLstUnlockRequestTableRows.ts | 4 +- apps/tangle-dapp/constants/liquidStaking.ts | 114 ++++++++---------- apps/tangle-dapp/data/liquidStaking/store.ts | 6 +- .../data/liquidStaking/useExchangeRate.ts | 4 +- .../liquidStaking/useLiquidStakingItems.ts | 51 ++++---- .../data/liquidStaking/useLstRebondTx.ts | 4 +- .../liquidStaking/useLstUnlockRequests.ts | 4 +- .../liquidStaking/useLstWithdrawRedeemTx.ts | 4 +- .../data/liquidStaking/useMintTx.ts | 4 +- .../data/liquidStaking/useOngoingTimeUnits.ts | 4 +- .../liquidStaking/useParachainBalances.ts | 6 +- .../data/liquidStaking/useRedeemTx.ts | 4 +- .../liquidStaking/useTokenUnlockLedger.ts | 4 +- .../data/liquifier/useLiquifier.ts | 17 +++ apps/tangle-dapp/hooks/useLSTokenSVGs.ts | 6 +- .../liquidStaking/isLiquidStakingToken.ts | 10 +- .../utils/liquidStaking/performTimeUnitOp.ts | 8 +- .../tangleTimeUnitToSimpleInstance.ts | 4 +- .../liquidStaking/timeUnitToMilliseconds.ts | 14 +-- 30 files changed, 191 insertions(+), 191 deletions(-) create mode 100644 apps/tangle-dapp/data/liquifier/useLiquifier.ts diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/ChainLogo.tsx b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/ChainLogo.tsx index e7ff73d5c..9e6232542 100644 --- a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/ChainLogo.tsx +++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/ChainLogo.tsx @@ -2,15 +2,12 @@ import Image from 'next/image'; import { FC } from 'react'; import { twMerge } from 'tailwind-merge'; -import { - PARACHAIN_CHAIN_MAP, - ParachainChainId, -} from '../../../constants/liquidStaking'; +import { LS_CHAIN_MAP, LsChainId } from '../../../constants/liquidStaking'; export type ChainLogoSize = 'sm' | 'md'; export type ChainLogoProps = { - chainId?: ParachainChainId; + chainId?: LsChainId; size: ChainLogoSize; isRounded?: boolean; isLiquidVariant?: boolean; @@ -34,21 +31,21 @@ const getSizeClass = (size: ChainLogoSize) => { } }; -const getBackgroundColor = (chain: ParachainChainId) => { +const getBackgroundColor = (chain: LsChainId) => { switch (chain) { - case ParachainChainId.MANTA: + case LsChainId.MANTA: return 'bg-[#13101D] dark:bg-[#13101D]'; - case ParachainChainId.MOONBEAM: + case LsChainId.MOONBEAM: return 'bg-[#1d1336] dark:bg-[#1d1336]'; - case ParachainChainId.PHALA: + case LsChainId.PHALA: return 'bg-black dark:bg-black'; - case ParachainChainId.POLKADOT: + case LsChainId.POLKADOT: return 'bg-mono-0 dark:bg-mono-0'; - case ParachainChainId.TANGLE_RESTAKING_PARACHAIN: + case LsChainId.TANGLE_RESTAKING_PARACHAIN: // Fix the icon SVG getting cut off on the sides by adding // a matching background. return 'bg-[#f6f4ff]'; - case ParachainChainId.ASTAR: + case LsChainId.ASTAR: // No background for Astar, since it looks better without // a background. return ''; @@ -88,7 +85,7 @@ const ChainLogo: FC = ({ getBackgroundColor(chainId), isRounded ? 'rounded-full' : 'rounded-md', )} - src={PARACHAIN_CHAIN_MAP[chainId].logo} + src={LS_CHAIN_MAP[chainId].logo} alt={`Logo of the liquid staking token ${chainId}`} width={sizeNumber} height={sizeNumber} diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/ExchangeRateDetailItem.tsx b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/ExchangeRateDetailItem.tsx index 7c1165ef7..605841150 100644 --- a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/ExchangeRateDetailItem.tsx +++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/ExchangeRateDetailItem.tsx @@ -2,7 +2,7 @@ import { SkeletonLoader } from '@webb-tools/webb-ui-components'; import { FC } from 'react'; import { - LiquidStakingToken, + LsToken, LST_PREFIX, ParachainCurrency, } from '../../../constants/liquidStaking'; @@ -13,7 +13,7 @@ import DetailItem from './DetailItem'; export type ExchangeRateDetailItemProps = { type: ExchangeRateType; - token: LiquidStakingToken; + token: LsToken; currency: ParachainCurrency; }; diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakeCard.tsx b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakeCard.tsx index 433d0ef1c..e73dd7547 100644 --- a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakeCard.tsx +++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakeCard.tsx @@ -18,8 +18,8 @@ import { FC, useCallback, useMemo, useState } from 'react'; import { LST_PREFIX, - PARACHAIN_CHAIN_MAP, - ParachainChainId, + LS_CHAIN_MAP, + LsChainId, } from '../../../constants/liquidStaking'; import { useLiquidStakingStore } from '../../../data/liquidStaking/store'; import useExchangeRate, { @@ -46,7 +46,7 @@ const LiquidStakeCard: FC = () => { const { execute: executeMintTx, status: mintTxStatus } = useMintTx(); const { nativeBalances } = useParachainBalances(); - const selectedChain = PARACHAIN_CHAIN_MAP[selectedChainId]; + const selectedChain = LS_CHAIN_MAP[selectedChainId]; const exchangeRate = useExchangeRate( ExchangeRateType.NativeToLiquid, @@ -133,7 +133,7 @@ const LiquidStakeCard: FC = () => { void; - setChainId?: (newChain: ParachainChainId) => void; + setChainId?: (newChain: LsChainId) => void; onTokenClick?: () => void; }; @@ -109,7 +109,7 @@ const LiquidStakingInput: FC = ({ )} >
- + {rightElement}
@@ -145,21 +145,22 @@ const LiquidStakingInput: FC = ({ }; type ChainSelectorProps = { - selectedChainId: ParachainChainId; + selectedChainId: LsChainId; /** * If this function is not provided, the selector will be * considered read-only. */ - setChain?: (newChain: ParachainChainId) => void; + setChainId?: (newChain: LsChainId) => void; }; /** @internal */ const ChainSelector: FC = ({ selectedChainId, - setChain, + setChainId, }) => { - const isReadOnly = setChain === undefined; + const isReadOnly = setChainId === undefined; + const selectedChainName = LS_CHAIN_MAP[selectedChainId].name; const base = (
@@ -167,7 +168,7 @@ const ChainSelector: FC = ({ - {LS_CHAIN_TO_NETWORK_NAME[selectedChainId]} + {selectedChainName}
@@ -175,24 +176,26 @@ const ChainSelector: FC = ({ ); - return setChain !== undefined ? ( + return setChainId !== undefined ? ( {base}
    - {Object.values(ParachainChainId) + {Object.values(LsChainId) .filter((chainId) => chainId !== selectedChainId) .map((chainId) => { + const chainName = LS_CHAIN_MAP[chainId].name; + return (
  • } - onSelect={() => setChain(chainId)} + onSelect={() => setChainId(chainId)} className="px-3 normal-case" > - {LS_CHAIN_TO_NETWORK_NAME[chainId]} + {chainName}
  • ); diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakingTokenItem.tsx b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakingTokenItem.tsx index 259f7056d..8bf44e9d3 100644 --- a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakingTokenItem.tsx +++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakingTokenItem.tsx @@ -8,9 +8,9 @@ import { FC, useMemo } from 'react'; import { StaticAssetPath } from '../../../constants'; import { - LiquidStakingToken, + LsToken, LST_PREFIX, - ParachainChainId, + LsChainId, TVS_TOOLTIP, } from '../../../constants/liquidStaking'; import { PagePath } from '../../../types'; @@ -19,9 +19,9 @@ import StatItem from '../StatItem'; import ChainLogo from './ChainLogo'; export type LiquidStakingTokenItemProps = { - chainId: ParachainChainId; + chainId: LsChainId; title: string; - tokenSymbol: LiquidStakingToken; + tokenSymbol: LsToken; totalValueStaked: number; totalStaked: string; }; diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidUnstakeCard.tsx b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidUnstakeCard.tsx index e984fd738..751d6ca19 100644 --- a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidUnstakeCard.tsx +++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidUnstakeCard.tsx @@ -12,8 +12,8 @@ import { FC, useCallback, useEffect, useMemo, useState } from 'react'; import { LST_PREFIX, - PARACHAIN_CHAIN_MAP, - ParachainChainId, + LS_CHAIN_MAP, + LsChainId, } from '../../../constants/liquidStaking'; import useDelegationsOccupiedStatus from '../../../data/liquidStaking/useDelegationsOccupiedStatus'; import useExchangeRate, { @@ -39,8 +39,8 @@ const LiquidUnstakeCard: FC = () => { const [isRequestSubmittedModalOpen, setIsRequestSubmittedModalOpen] = useState(false); - const [selectedChainId, setSelectedChainId] = useState( - ParachainChainId.TANGLE_RESTAKING_PARACHAIN, + const [selectedChainId, setSelectedChainId] = useState( + LsChainId.TANGLE_RESTAKING_PARACHAIN, ); const { @@ -51,7 +51,7 @@ const LiquidUnstakeCard: FC = () => { const { liquidBalances } = useParachainBalances(); - const selectedChain = PARACHAIN_CHAIN_MAP[selectedChainId]; + const selectedChain = LS_CHAIN_MAP[selectedChainId]; const exchangeRate = useExchangeRate( ExchangeRateType.LiquidToNative, @@ -150,7 +150,7 @@ const LiquidUnstakeCard: FC = () => { {/* TODO: Have a way to trigger a refresh of the amount once the wallet balance (max) button is clicked. Need to signal to the liquid staking input to update its display amount based on the `fromAmount` prop. */} = ({ diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/ParachainWalletBalance.tsx b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/ParachainWalletBalance.tsx index 76860028e..27b675cdf 100644 --- a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/ParachainWalletBalance.tsx +++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/ParachainWalletBalance.tsx @@ -13,14 +13,14 @@ import { FC, useCallback, useMemo, useState } from 'react'; import { twMerge } from 'tailwind-merge'; import { EMPTY_VALUE_PLACEHOLDER } from '../../../constants'; -import { LiquidStakingToken } from '../../../constants/liquidStaking'; +import { LsToken } from '../../../constants/liquidStaking'; import useParachainBalances from '../../../data/liquidStaking/useParachainBalances'; import useSubstrateAddress from '../../../hooks/useSubstrateAddress'; import formatBn from '../../../utils/formatBn'; export type ParachainWalletBalanceProps = { isNative?: boolean; - token: LiquidStakingToken; + token: LsToken; decimals: number; tooltip?: string; onlyShowTooltipWhenBalanceIsSet?: boolean; diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/SelectTokenModal.tsx b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/SelectTokenModal.tsx index a98c2677b..459a79fab 100644 --- a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/SelectTokenModal.tsx +++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/SelectTokenModal.tsx @@ -8,7 +8,7 @@ import { } from '@webb-tools/webb-ui-components'; import { FC, useEffect, useMemo } from 'react'; -import { ParachainChainId } from '../../../constants/liquidStaking'; +import { LsChainId } from '../../../constants/liquidStaking'; import { AnySubstrateAddress } from '../../../types/utils'; import formatBn from '../../../utils/formatBn'; import AddressLink from '../AddressLink'; @@ -108,7 +108,7 @@ const TokenListItem: FC = ({ > {/* Information */}
    - +
    diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/TokenChip.tsx b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/TokenChip.tsx index ed52a7af8..b09bda068 100644 --- a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/TokenChip.tsx +++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/TokenChip.tsx @@ -5,14 +5,14 @@ import { twMerge } from 'tailwind-merge'; import { LIQUID_STAKING_CHAINS, - LiquidStakingToken, + LsToken, LST_PREFIX, } from '../../../constants/liquidStaking'; import ChainLogo from './ChainLogo'; import DropdownChevronIcon from './DropdownChevronIcon'; type TokenChipProps = { - token?: LiquidStakingToken; + token?: LsToken; isLiquidVariant: boolean; onClick?: () => void; }; diff --git a/apps/tangle-dapp/components/LiquidStaking/unstakeRequestsTable/UnstakeRequestsTable.tsx b/apps/tangle-dapp/components/LiquidStaking/unstakeRequestsTable/UnstakeRequestsTable.tsx index c2052bcc5..06ebd112a 100644 --- a/apps/tangle-dapp/components/LiquidStaking/unstakeRequestsTable/UnstakeRequestsTable.tsx +++ b/apps/tangle-dapp/components/LiquidStaking/unstakeRequestsTable/UnstakeRequestsTable.tsx @@ -29,7 +29,7 @@ import { twMerge } from 'tailwind-merge'; import { ParachainCurrency, - SimpleTimeUnitInstance, + LsSimpleParachainTimeUnit, } from '../../../constants/liquidStaking'; import useSubstrateAddress from '../../../hooks/useSubstrateAddress'; import { AnySubstrateAddress } from '../../../types/utils'; @@ -57,7 +57,7 @@ export type UnstakeRequestTableRow = { * If this is undefined, it means that the request has * completed its unlocking period. */ - remainingTimeUnit?: SimpleTimeUnitInstance; + remainingTimeUnit?: LsSimpleParachainTimeUnit; }; const columnHelper = createColumnHelper(); diff --git a/apps/tangle-dapp/components/LiquidStaking/unstakeRequestsTable/useLstUnlockRequestTableRows.ts b/apps/tangle-dapp/components/LiquidStaking/unstakeRequestsTable/useLstUnlockRequestTableRows.ts index ea4f29a9d..9d9ce5f0c 100644 --- a/apps/tangle-dapp/components/LiquidStaking/unstakeRequestsTable/useLstUnlockRequestTableRows.ts +++ b/apps/tangle-dapp/components/LiquidStaking/unstakeRequestsTable/useLstUnlockRequestTableRows.ts @@ -3,7 +3,7 @@ import { useMemo } from 'react'; import { LIQUID_STAKING_CHAINS, - SimpleTimeUnitInstance, + LsSimpleParachainTimeUnit, } from '../../../constants/liquidStaking'; import useLstUnlockRequests from '../../../data/liquidStaking/useLstUnlockRequests'; import useOngoingTimeUnits from '../../../data/liquidStaking/useOngoingTimeUnits'; @@ -56,7 +56,7 @@ const useLstUnlockRequestTableRows = () => { const remainingTimeUnitValue = request.unlockTimeUnit.value - ongoingTimeUnitEntry.timeUnit.value; - const remainingTimeUnit: SimpleTimeUnitInstance | undefined = + const remainingTimeUnit: LsSimpleParachainTimeUnit | undefined = remainingTimeUnitValue <= 0 ? undefined : { diff --git a/apps/tangle-dapp/constants/liquidStaking.ts b/apps/tangle-dapp/constants/liquidStaking.ts index 5d6b48b25..3fe1b90e1 100644 --- a/apps/tangle-dapp/constants/liquidStaking.ts +++ b/apps/tangle-dapp/constants/liquidStaking.ts @@ -7,22 +7,24 @@ import { TANGLE_RESTAKING_PARACHAIN_LOCAL_DEV_NETWORK } from '@webb-tools/webb-u import { StaticAssetPath } from '.'; -export enum ParachainChainId { +export enum LsChainId { POLKADOT = 'Polkadot', PHALA = 'Phala', MOONBEAM = 'Moonbeam', ASTAR = 'Astar', MANTA = 'Manta', TANGLE_RESTAKING_PARACHAIN = 'Tangle Parachain', + CHAINLINK = 'Chainlink', } -export enum LiquidStakingToken { +export enum LsToken { DOT = 'DOT', GLMR = 'GLMR', MANTA = 'MANTA', ASTAR = 'ASTR', PHALA = 'PHALA', TNT = 'TNT', + LINK = 'LINK', } // TODO: Temporary manual override until the Parachain types are updated. @@ -30,28 +32,21 @@ export type ParachainCurrency = TanglePrimitivesCurrencyTokenSymbol['type']; // | Exclude // | 'Tnt'; -export type SubstrateChainTimingSpec = { - expectedBlockTimeMs: number; - blocksPerSession: number; - sessionsPerEra: number; -}; - -export type ParachainChainDef = { - id: ParachainChainId; +export type LsChainDef = { + id: LsChainId; name: string; - token: LiquidStakingToken; + token: LsToken; logo: StaticAssetPath; networkName: string; currency: ParachainCurrency; decimals: number; - substrateTimingSpec?: SubstrateChainTimingSpec; rpcEndpoint: string; }; -const POLKADOT: ParachainChainDef = { - id: ParachainChainId.POLKADOT, +const POLKADOT: LsChainDef = { + id: LsChainId.POLKADOT, name: 'Polkadot', - token: LiquidStakingToken.DOT, + token: LsToken.DOT, logo: StaticAssetPath.LIQUID_STAKING_TOKEN_POLKADOT, networkName: 'Polkadot Mainnet', currency: 'Dot', @@ -59,10 +54,10 @@ const POLKADOT: ParachainChainDef = { rpcEndpoint: 'wss://polkadot-rpc.dwellir.com', }; -const PHALA: ParachainChainDef = { - id: ParachainChainId.PHALA, +const PHALA: LsChainDef = { + id: LsChainId.PHALA, name: 'Phala', - token: LiquidStakingToken.PHALA, + token: LsToken.PHALA, logo: StaticAssetPath.LIQUID_STAKING_TOKEN_PHALA, networkName: 'Phala', currency: 'Pha', @@ -70,10 +65,10 @@ const PHALA: ParachainChainDef = { rpcEndpoint: 'wss://api.phala.network/ws', }; -const MOONBEAM: ParachainChainDef = { - id: ParachainChainId.MOONBEAM, +const MOONBEAM: LsChainDef = { + id: LsChainId.MOONBEAM, name: 'Moonbeam', - token: LiquidStakingToken.GLMR, + token: LsToken.GLMR, logo: StaticAssetPath.LIQUID_STAKING_TOKEN_GLIMMER, networkName: 'Moonbeam', // TODO: No currency entry for GLMR in the Tangle Primitives? @@ -82,10 +77,10 @@ const MOONBEAM: ParachainChainDef = { rpcEndpoint: 'wss://moonbeam.api.onfinality.io/public-ws', }; -const ASTAR: ParachainChainDef = { - id: ParachainChainId.ASTAR, +const ASTAR: LsChainDef = { + id: LsChainId.ASTAR, name: 'Astar', - token: LiquidStakingToken.ASTAR, + token: LsToken.ASTAR, logo: StaticAssetPath.LIQUID_STAKING_TOKEN_ASTAR, networkName: 'Astar', // TODO: No currency entry for ASTAR in the Tangle Primitives? @@ -94,10 +89,10 @@ const ASTAR: ParachainChainDef = { rpcEndpoint: 'wss://astar.api.onfinality.io/public-ws', }; -const MANTA: ParachainChainDef = { - id: ParachainChainId.MANTA, +const MANTA: LsChainDef = { + id: LsChainId.MANTA, name: 'Manta', - token: LiquidStakingToken.MANTA, + token: LsToken.MANTA, logo: StaticAssetPath.LIQUID_STAKING_TOKEN_MANTA, networkName: 'Manta', // TODO: No currency entry for ASTAR in the Tangle Primitives? @@ -106,46 +101,41 @@ const MANTA: ParachainChainDef = { rpcEndpoint: 'wss://ws.manta.systems', }; -const TANGLE_RESTAKING_PARACHAIN: ParachainChainDef = { - id: ParachainChainId.TANGLE_RESTAKING_PARACHAIN, +const TANGLE_RESTAKING_PARACHAIN: LsChainDef = { + id: LsChainId.TANGLE_RESTAKING_PARACHAIN, name: 'Tangle Parachain', - token: LiquidStakingToken.TNT, + token: LsToken.TNT, logo: StaticAssetPath.LIQUID_STAKING_TANGLE_LOGO, networkName: 'Tangle Parachain', currency: 'Bnc', decimals: TANGLE_TOKEN_DECIMALS, rpcEndpoint: TANGLE_RESTAKING_PARACHAIN_LOCAL_DEV_NETWORK.wsRpcEndpoint, - substrateTimingSpec: { - expectedBlockTimeMs: 12_000, - // TODO: This is dummy data for now. Update with the actual values. - blocksPerSession: 6, - sessionsPerEra: 6, - }, }; -export const PARACHAIN_CHAIN_MAP: Record = - { - [ParachainChainId.POLKADOT]: POLKADOT, - [ParachainChainId.PHALA]: PHALA, - [ParachainChainId.MOONBEAM]: MOONBEAM, - [ParachainChainId.ASTAR]: ASTAR, - [ParachainChainId.MANTA]: MANTA, - [ParachainChainId.TANGLE_RESTAKING_PARACHAIN]: TANGLE_RESTAKING_PARACHAIN, - }; - -export const LIQUID_STAKING_CHAINS: ParachainChainDef[] = - Object.values(PARACHAIN_CHAIN_MAP); - -// TODO: Instead of mapping to names, map to network/chain definitions themselves. This avoids redundancy and relies on a centralized definition for the network/chain which is better, since it simplifies future refactoring. -export const LS_CHAIN_TO_NETWORK_NAME: Record = { - [ParachainChainId.POLKADOT]: 'Polkadot Mainnet', - [ParachainChainId.PHALA]: 'Phala', - [ParachainChainId.MOONBEAM]: 'Moonbeam', - [ParachainChainId.ASTAR]: 'Astar', - [ParachainChainId.MANTA]: 'Manta', - [ParachainChainId.TANGLE_RESTAKING_PARACHAIN]: 'Tangle Parachain', +const CHAINLINK: LsChainDef = { + id: LsChainId.CHAINLINK, + name: 'Chainlink', + token: LsToken.LINK, + networkName: 'Chainlink', + decimals: 18, + // TODO: Dummy data. Need to differentiate between EVM and Substrate chains. + currency: 'Asg', + logo: StaticAssetPath.LIQUID_STAKING_TANGLE_LOGO, + rpcEndpoint: 'wss://api.chain.link', }; +export const LS_CHAIN_MAP: Record = { + [LsChainId.POLKADOT]: POLKADOT, + [LsChainId.PHALA]: PHALA, + [LsChainId.MOONBEAM]: MOONBEAM, + [LsChainId.ASTAR]: ASTAR, + [LsChainId.MANTA]: MANTA, + [LsChainId.TANGLE_RESTAKING_PARACHAIN]: TANGLE_RESTAKING_PARACHAIN, + [LsChainId.CHAINLINK]: CHAINLINK, +}; + +export const LIQUID_STAKING_CHAINS: LsChainDef[] = Object.values(LS_CHAIN_MAP); + export const TVS_TOOLTIP = "Total Value Staked (TVS) refers to the total value of assets that are currently staked for this network in fiat currency. Generally used as an indicator of a network's security and trustworthiness."; @@ -155,7 +145,7 @@ export const LST_PREFIX = 'tg'; export type Network = { name: string; endpoint: string; - tokenSymbol: LiquidStakingToken; + tokenSymbol: LsToken; chainType: NetworkType; }; @@ -164,13 +154,13 @@ export enum NetworkType { PARACHAIN = 'Parachain', } -export type ParachainCurrencyKey = +export type LsParachainCurrencyKey = | { lst: ParachainCurrency } | { Native: ParachainCurrency }; -export type ParachainTimeUnit = TanglePrimitivesTimeUnit['type']; +export type LsParachainTimeUnit = TanglePrimitivesTimeUnit['type']; -export type SimpleTimeUnitInstance = { +export type LsSimpleParachainTimeUnit = { value: number; - unit: ParachainTimeUnit; + unit: LsParachainTimeUnit; }; diff --git a/apps/tangle-dapp/data/liquidStaking/store.ts b/apps/tangle-dapp/data/liquidStaking/store.ts index bbb2b9cb8..5619888f0 100644 --- a/apps/tangle-dapp/data/liquidStaking/store.ts +++ b/apps/tangle-dapp/data/liquidStaking/store.ts @@ -1,9 +1,9 @@ import { create } from 'zustand'; -import { ParachainChainId } from '../../constants/liquidStaking'; +import { LsChainId } from '../../constants/liquidStaking'; type State = { - selectedChainId: ParachainChainId; + selectedChainId: LsChainId; selectedItems: Set; }; @@ -15,7 +15,7 @@ type Actions = { type Store = State & Actions; export const useLiquidStakingStore = create((set) => ({ - selectedChainId: ParachainChainId.TANGLE_RESTAKING_PARACHAIN, + selectedChainId: LsChainId.TANGLE_RESTAKING_PARACHAIN, selectedItems: new Set(), setSelectedChainId: (selectedChainId) => set({ selectedChainId }), setSelectedItems: (selectedItems) => set({ selectedItems }), diff --git a/apps/tangle-dapp/data/liquidStaking/useExchangeRate.ts b/apps/tangle-dapp/data/liquidStaking/useExchangeRate.ts index 74eac05af..1981de00d 100644 --- a/apps/tangle-dapp/data/liquidStaking/useExchangeRate.ts +++ b/apps/tangle-dapp/data/liquidStaking/useExchangeRate.ts @@ -3,7 +3,7 @@ import { useMemo } from 'react'; import { ParachainCurrency, - ParachainCurrencyKey, + LsParachainCurrencyKey, } from '../../constants/liquidStaking'; import useApiRx from '../../hooks/useApiRx'; import calculateBnRatio from '../../utils/calculateBnRatio'; @@ -18,7 +18,7 @@ const useExchangeRate = ( currency: ParachainCurrency, ) => { const { result: tokenPoolAmount } = useApiRx((api) => { - const key: ParachainCurrencyKey = + const key: LsParachainCurrencyKey = type === ExchangeRateType.NativeToLiquid ? { Native: currency } : { lst: currency }; diff --git a/apps/tangle-dapp/data/liquidStaking/useLiquidStakingItems.ts b/apps/tangle-dapp/data/liquidStaking/useLiquidStakingItems.ts index 41314846e..3877de580 100644 --- a/apps/tangle-dapp/data/liquidStaking/useLiquidStakingItems.ts +++ b/apps/tangle-dapp/data/liquidStaking/useLiquidStakingItems.ts @@ -1,10 +1,7 @@ import { BN_ZERO } from '@polkadot/util'; import { useCallback, useEffect, useMemo, useState } from 'react'; -import { - PARACHAIN_CHAIN_MAP, - ParachainChainId, -} from '../../constants/liquidStaking'; +import { LS_CHAIN_MAP, LsChainId } from '../../constants/liquidStaking'; import useLocalStorage, { LocalStorageKey } from '../../hooks/useLocalStorage'; import { Collator, @@ -27,7 +24,7 @@ import { fetchVaultsAndStakePools, } from './helper'; -const useLiquidStakingItems = (selectedChain: ParachainChainId) => { +const useLiquidStakingItems = (selectedChain: LsChainId) => { const { // valueOpt: liquidStakingTableData, setWithPreviousValue: setLiquidStakingTableData, @@ -45,8 +42,8 @@ const useLiquidStakingItems = (selectedChain: ParachainChainId) => { // console.debug('cachedData', cachedData); const fetchData = useCallback( - async (chain: ParachainChainId) => { - const endpoint = PARACHAIN_CHAIN_MAP[chain]?.rpcEndpoint; + async (chain: LsChainId) => { + const endpoint = LS_CHAIN_MAP[chain]?.rpcEndpoint; if (!endpoint) { setItems([]); @@ -58,30 +55,30 @@ const useLiquidStakingItems = (selectedChain: ParachainChainId) => { []; switch (chain) { - case ParachainChainId.POLKADOT: + case LsChainId.POLKADOT: fetchedItems = await getValidators(endpoint); break; - case ParachainChainId.ASTAR: + case LsChainId.ASTAR: fetchedItems = await getDapps(endpoint); break; - case ParachainChainId.PHALA: + case LsChainId.PHALA: fetchedItems = await getVaultsAndStakePools(endpoint); break; - case ParachainChainId.MOONBEAM: + case LsChainId.MOONBEAM: fetchedItems = await getCollators( endpoint, - ParachainChainId.MOONBEAM, + LsChainId.MOONBEAM, 'https://stakeglmr.com/', ); break; - case ParachainChainId.MANTA: + case LsChainId.MANTA: fetchedItems = await getCollators( endpoint, - ParachainChainId.MANTA, + LsChainId.MANTA, 'https://manta.subscan.io/account/', ); break; @@ -117,18 +114,18 @@ const useLiquidStakingItems = (selectedChain: ParachainChainId) => { export default useLiquidStakingItems; -const getDataType = (chain: ParachainChainId) => { +const getDataType = (chain: LsChainId) => { switch (chain) { - case ParachainChainId.MANTA: + case LsChainId.MANTA: return LiquidStakingItem.COLLATOR; - case ParachainChainId.MOONBEAM: + case LsChainId.MOONBEAM: return LiquidStakingItem.COLLATOR; - case ParachainChainId.TANGLE_RESTAKING_PARACHAIN: - case ParachainChainId.POLKADOT: + case LsChainId.TANGLE_RESTAKING_PARACHAIN: + case LsChainId.POLKADOT: return LiquidStakingItem.VALIDATOR; - case ParachainChainId.PHALA: + case LsChainId.PHALA: return LiquidStakingItem.VAULT_OR_STAKE_POOL; - case ParachainChainId.ASTAR: + case LsChainId.ASTAR: return LiquidStakingItem.DAPP; default: return LiquidStakingItem.VALIDATOR; @@ -164,7 +161,7 @@ const getValidators = async (endpoint: string): Promise => { totalValueStaked: totalValueStaked || BN_ZERO, validatorAPY: 0, validatorCommission: commission || BN_ZERO, - chain: ParachainChainId.POLKADOT, + chain: LsChainId.POLKADOT, chainDecimals, chainTokenSymbol, itemType: LiquidStakingItem.VALIDATOR, @@ -211,7 +208,7 @@ const getDapps = async (endpoint: string): Promise => { dappContractType: dappInfo ? dappInfo.contractType || '' : '', commission: BN_ZERO, totalValueStaked: totalValueStaked || BN_ZERO, - chain: ParachainChainId.ASTAR, + chain: LsChainId.ASTAR, chainDecimals, chainTokenSymbol, itemType: LiquidStakingItem.DAPP, @@ -241,7 +238,7 @@ const getVaultsAndStakePools = async ( vaultOrStakePoolName: val.id, commission: val.commission, totalValueStaked: val.totalValueStaked, - chain: ParachainChainId.PHALA, + chain: LsChainId.PHALA, chainDecimals, chainTokenSymbol, type, @@ -253,7 +250,7 @@ const getVaultsAndStakePools = async ( const getCollators = async ( endpoint: string, - chain: ParachainChainId, + chain: LsChainId, href: string, ): Promise => { const [ @@ -276,9 +273,9 @@ const getCollators = async ( let collatorExternalLink = ''; - if (chain === ParachainChainId.MOONBEAM) { + if (chain === LsChainId.MOONBEAM) { collatorExternalLink = href; - } else if (chain === ParachainChainId.MANTA) { + } else if (chain === LsChainId.MANTA) { collatorExternalLink = href + collator; } diff --git a/apps/tangle-dapp/data/liquidStaking/useLstRebondTx.ts b/apps/tangle-dapp/data/liquidStaking/useLstRebondTx.ts index bb7622771..7bc4f46e4 100644 --- a/apps/tangle-dapp/data/liquidStaking/useLstRebondTx.ts +++ b/apps/tangle-dapp/data/liquidStaking/useLstRebondTx.ts @@ -3,7 +3,7 @@ import { TANGLE_RESTAKING_PARACHAIN_LOCAL_DEV_NETWORK } from '@webb-tools/webb-u import { TxName } from '../../constants'; import { ParachainCurrency, - ParachainCurrencyKey, + LsParachainCurrencyKey, } from '../../constants/liquidStaking'; import { useSubstrateTxWithNotification } from '../../hooks/useSubstrateTx'; import optimizeTxBatch from '../../utils/optimizeTxBatch'; @@ -19,7 +19,7 @@ const useLstRebondTx = () => { TxName.LST_REBOND, (api, _activeSubstrateAddress, context) => { const txs = context.currenciesAndUnlockIds.map(([currency, unlockId]) => { - const key: ParachainCurrencyKey = { Native: currency }; + const key: LsParachainCurrencyKey = { Native: currency }; return api.tx.lstMinting.rebondByUnlockId(key, unlockId); }); diff --git a/apps/tangle-dapp/data/liquidStaking/useLstUnlockRequests.ts b/apps/tangle-dapp/data/liquidStaking/useLstUnlockRequests.ts index 65b8516aa..49e71027c 100644 --- a/apps/tangle-dapp/data/liquidStaking/useLstUnlockRequests.ts +++ b/apps/tangle-dapp/data/liquidStaking/useLstUnlockRequests.ts @@ -13,7 +13,7 @@ import { map } from 'rxjs'; import { ParachainCurrency, - SimpleTimeUnitInstance, + LsSimpleParachainTimeUnit, } from '../../constants/liquidStaking'; import useApiRx from '../../hooks/useApiRx'; import useSubstrateAddress from '../../hooks/useSubstrateAddress'; @@ -25,7 +25,7 @@ export type LstUnlockRequest = { unlockId: number; currencyType: TanglePrimitivesCurrencyCurrencyId['type']; currency: ParachainCurrency; - unlockTimeUnit: SimpleTimeUnitInstance; + unlockTimeUnit: LsSimpleParachainTimeUnit; amount: BN; }; diff --git a/apps/tangle-dapp/data/liquidStaking/useLstWithdrawRedeemTx.ts b/apps/tangle-dapp/data/liquidStaking/useLstWithdrawRedeemTx.ts index 9369cccfa..a3261bd74 100644 --- a/apps/tangle-dapp/data/liquidStaking/useLstWithdrawRedeemTx.ts +++ b/apps/tangle-dapp/data/liquidStaking/useLstWithdrawRedeemTx.ts @@ -3,7 +3,7 @@ import { useCallback } from 'react'; import { TxName } from '../../constants'; import { ParachainCurrency, - ParachainCurrencyKey, + LsParachainCurrencyKey, } from '../../constants/liquidStaking'; import { useSubstrateTxWithNotification } from '../../hooks/useSubstrateTx'; import optimizeTxBatch from '../../utils/optimizeTxBatch'; @@ -17,7 +17,7 @@ const useLstWithdrawRedeemTx = () => { TxName.LST_WITHDRAW_REDEEM, useCallback((api, _activeSubstrateAddress, context) => { const txs = context.currenciesAndUnlockIds.map(([currency, unlockId]) => { - const key: ParachainCurrencyKey = { Native: currency }; + const key: LsParachainCurrencyKey = { Native: currency }; // TODO: This should be `withdrawRedeem`, but the type defs of the restaking parachain haven't been updated yet. So, this is only temporary/dummy data. Once it is implemented, it should be a quick change here. return api.tx.lstMinting.rebondByUnlockId(key, unlockId); diff --git a/apps/tangle-dapp/data/liquidStaking/useMintTx.ts b/apps/tangle-dapp/data/liquidStaking/useMintTx.ts index 2c3a49073..811d0d8bc 100644 --- a/apps/tangle-dapp/data/liquidStaking/useMintTx.ts +++ b/apps/tangle-dapp/data/liquidStaking/useMintTx.ts @@ -9,7 +9,7 @@ import { TANGLE_RESTAKING_PARACHAIN_LOCAL_DEV_NETWORK } from '@webb-tools/webb-u import { TxName } from '../../constants'; import { ParachainCurrency, - ParachainCurrencyKey, + LsParachainCurrencyKey, } from '../../constants/liquidStaking'; import { useSubstrateTxWithNotification } from '../../hooks/useSubstrateTx'; @@ -24,7 +24,7 @@ const useMintTx = () => { return useSubstrateTxWithNotification( TxName.LST_MINT, (api, _activeSubstrateAddress, context) => { - const key: ParachainCurrencyKey = { Native: context.currency }; + const key: LsParachainCurrencyKey = { Native: context.currency }; // TODO: Investigate what the `remark` and `channel` parameters are for, and whether they are relevant for us here. // The remark field can be used to store additional information about diff --git a/apps/tangle-dapp/data/liquidStaking/useOngoingTimeUnits.ts b/apps/tangle-dapp/data/liquidStaking/useOngoingTimeUnits.ts index 284ca11f6..58a7acba5 100644 --- a/apps/tangle-dapp/data/liquidStaking/useOngoingTimeUnits.ts +++ b/apps/tangle-dapp/data/liquidStaking/useOngoingTimeUnits.ts @@ -4,7 +4,7 @@ import { useCallback, useMemo } from 'react'; import { ParachainCurrency, - SimpleTimeUnitInstance, + LsSimpleParachainTimeUnit, } from '../../constants/liquidStaking'; import useApiRx from '../../hooks/useApiRx'; import tangleTimeUnitToSimpleInstance from '../../utils/liquidStaking/tangleTimeUnitToSimpleInstance'; @@ -13,7 +13,7 @@ import getValueOfTangleCurrency from './getValueOfTangleCurrency'; export type OngoingTimeUnitEntry = { currencyType: TanglePrimitivesCurrencyCurrencyId['type']; currency: ParachainCurrency; - timeUnit: SimpleTimeUnitInstance; + timeUnit: LsSimpleParachainTimeUnit; }; const useOngoingTimeUnits = () => { diff --git a/apps/tangle-dapp/data/liquidStaking/useParachainBalances.ts b/apps/tangle-dapp/data/liquidStaking/useParachainBalances.ts index bffd30701..b8664a2bc 100644 --- a/apps/tangle-dapp/data/liquidStaking/useParachainBalances.ts +++ b/apps/tangle-dapp/data/liquidStaking/useParachainBalances.ts @@ -8,7 +8,7 @@ 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 { LiquidStakingToken } from '../../constants/liquidStaking'; +import { LsToken } from '../../constants/liquidStaking'; import useApiRx from '../../hooks/useApiRx'; import useSubstrateAddress from '../../hooks/useSubstrateAddress'; import isLiquidStakingToken from '../../utils/liquidStaking/isLiquidStakingToken'; @@ -35,8 +35,8 @@ const useParachainBalances = () => { return null; } - const nativeBalances = new Map(); - const liquidBalances = new Map(); + const nativeBalances = new Map(); + const liquidBalances = new Map(); for (const encodedBalance of rawBalances) { // TODO: Need proper typing, ideally linked to Restaking Parachain's types. This is currently very hacky. diff --git a/apps/tangle-dapp/data/liquidStaking/useRedeemTx.ts b/apps/tangle-dapp/data/liquidStaking/useRedeemTx.ts index 49d337aa7..67be68dcf 100644 --- a/apps/tangle-dapp/data/liquidStaking/useRedeemTx.ts +++ b/apps/tangle-dapp/data/liquidStaking/useRedeemTx.ts @@ -8,7 +8,7 @@ import { TANGLE_RESTAKING_PARACHAIN_LOCAL_DEV_NETWORK } from '@webb-tools/webb-u import { TxName } from '../../constants'; import { ParachainCurrency, - ParachainCurrencyKey, + LsParachainCurrencyKey, } from '../../constants/liquidStaking'; import { useSubstrateTxWithNotification } from '../../hooks/useSubstrateTx'; @@ -24,7 +24,7 @@ const useRedeemTx = () => { return useSubstrateTxWithNotification( TxName.LST_REDEEM, (api, _activeSubstrateAddress, context) => { - const key: ParachainCurrencyKey = { lst: context.currency }; + const key: LsParachainCurrencyKey = { lst: context.currency }; return api.tx.lstMinting.redeem(key, context.amount); }, diff --git a/apps/tangle-dapp/data/liquidStaking/useTokenUnlockLedger.ts b/apps/tangle-dapp/data/liquidStaking/useTokenUnlockLedger.ts index 660cb0771..f5038ae4f 100644 --- a/apps/tangle-dapp/data/liquidStaking/useTokenUnlockLedger.ts +++ b/apps/tangle-dapp/data/liquidStaking/useTokenUnlockLedger.ts @@ -5,13 +5,13 @@ import '@webb-tools/tangle-restaking-types'; import { TANGLE_RESTAKING_PARACHAIN_LOCAL_DEV_NETWORK } from '@webb-tools/webb-ui-components/constants/networks'; import { useCallback, useMemo } from 'react'; -import { ParachainCurrencyKey } from '../../constants/liquidStaking'; +import { LsParachainCurrencyKey } from '../../constants/liquidStaking'; import useApiRx from '../../hooks/useApiRx'; import useSubstrateAddress from '../../hooks/useSubstrateAddress'; import tangleTimeUnitToSimpleInstance from '../../utils/liquidStaking/tangleTimeUnitToSimpleInstance'; // TODO: Providing a key here doesn't make much sense; find a way to get all the entries for the current account, without needing to provide a key. -const useTokenUnlockLedger = (key: ParachainCurrencyKey) => { +const useTokenUnlockLedger = (key: LsParachainCurrencyKey) => { const substrateAddress = useSubstrateAddress(); const { result: entries } = useApiRx( diff --git a/apps/tangle-dapp/data/liquifier/useLiquifier.ts b/apps/tangle-dapp/data/liquifier/useLiquifier.ts new file mode 100644 index 000000000..8927c9408 --- /dev/null +++ b/apps/tangle-dapp/data/liquifier/useLiquifier.ts @@ -0,0 +1,17 @@ +import { useMemo } from 'react'; +import { createPublicClient, http } from 'viem'; +import { mainnet } from 'viem/chains'; + +const useLiquifier = () => { + // TODO: Generate ABI using Remix, based on Solidity contracts. Then, attach the ABI to the client. + const client = useMemo(() => { + return createPublicClient({ + chain: mainnet, + transport: http(), + }); + }, []); + + // TODO: Implement. +}; + +export default useLiquifier; diff --git a/apps/tangle-dapp/hooks/useLSTokenSVGs.ts b/apps/tangle-dapp/hooks/useLSTokenSVGs.ts index d4fd94b8b..82192375e 100644 --- a/apps/tangle-dapp/hooks/useLSTokenSVGs.ts +++ b/apps/tangle-dapp/hooks/useLSTokenSVGs.ts @@ -6,10 +6,10 @@ import PHALA from '@webb-tools/icons/LiquidStakingTokens/PHALA.svg'; import TNT from '@webb-tools/icons/LiquidStakingTokens/TNT.svg'; import React, { useMemo } from 'react'; -import { LiquidStakingToken } from '../constants/liquidStaking'; +import { LsToken } from '../constants/liquidStaking'; const tokenSVGs: { - [key in LiquidStakingToken]: React.FC>; + [key in LsToken]: React.FC>; } = { DOT, GLMR, @@ -20,7 +20,7 @@ const tokenSVGs: { }; const useLSTokenSVGs = ( - tokenSymbol: LiquidStakingToken, + tokenSymbol: LsToken, ): React.FC> | null => { const TokenSVG = useMemo(() => tokenSVGs[tokenSymbol] || null, [tokenSymbol]); diff --git a/apps/tangle-dapp/utils/liquidStaking/isLiquidStakingToken.ts b/apps/tangle-dapp/utils/liquidStaking/isLiquidStakingToken.ts index 5b3d7fc61..cc7b7555c 100644 --- a/apps/tangle-dapp/utils/liquidStaking/isLiquidStakingToken.ts +++ b/apps/tangle-dapp/utils/liquidStaking/isLiquidStakingToken.ts @@ -1,11 +1,7 @@ -import { LiquidStakingToken } from '../../constants/liquidStaking'; +import { LsToken } from '../../constants/liquidStaking'; -function isLiquidStakingToken( - tokenSymbol: string, -): tokenSymbol is LiquidStakingToken { - return Object.values(LiquidStakingToken).includes( - tokenSymbol as LiquidStakingToken, - ); +function isLiquidStakingToken(tokenSymbol: string): tokenSymbol is LsToken { + return Object.values(LsToken).includes(tokenSymbol as LsToken); } export default isLiquidStakingToken; diff --git a/apps/tangle-dapp/utils/liquidStaking/performTimeUnitOp.ts b/apps/tangle-dapp/utils/liquidStaking/performTimeUnitOp.ts index d07d3bb14..cffb8b563 100644 --- a/apps/tangle-dapp/utils/liquidStaking/performTimeUnitOp.ts +++ b/apps/tangle-dapp/utils/liquidStaking/performTimeUnitOp.ts @@ -1,12 +1,12 @@ import assert from 'assert'; -import { SimpleTimeUnitInstance } from '../../constants/liquidStaking'; +import { LsSimpleParachainTimeUnit } from '../../constants/liquidStaking'; const performTimeUnitOp = ( operation: (a: number, b: number) => number, - a: SimpleTimeUnitInstance, - b: SimpleTimeUnitInstance, -): SimpleTimeUnitInstance => { + a: LsSimpleParachainTimeUnit, + b: LsSimpleParachainTimeUnit, +): LsSimpleParachainTimeUnit => { assert( a.unit === b.unit, `Time units must be of the same type, otherwise operations are not possible; Received: ${a.unit} and ${b.unit}`, diff --git a/apps/tangle-dapp/utils/liquidStaking/tangleTimeUnitToSimpleInstance.ts b/apps/tangle-dapp/utils/liquidStaking/tangleTimeUnitToSimpleInstance.ts index 4ae2616f2..07bfbd82b 100644 --- a/apps/tangle-dapp/utils/liquidStaking/tangleTimeUnitToSimpleInstance.ts +++ b/apps/tangle-dapp/utils/liquidStaking/tangleTimeUnitToSimpleInstance.ts @@ -1,11 +1,11 @@ import { TanglePrimitivesTimeUnit } from '@polkadot/types/lookup'; -import { SimpleTimeUnitInstance } from '../../constants/liquidStaking'; +import { LsSimpleParachainTimeUnit } from '../../constants/liquidStaking'; import getValueOfTangleTimeUnit from './getValueOfTangleTimeUnit'; const tangleTimeUnitToSimpleInstance = ( tangleTimeUnit: TanglePrimitivesTimeUnit, -): SimpleTimeUnitInstance => { +): LsSimpleParachainTimeUnit => { return { unit: tangleTimeUnit.type, value: getValueOfTangleTimeUnit(tangleTimeUnit), diff --git a/apps/tangle-dapp/utils/liquidStaking/timeUnitToMilliseconds.ts b/apps/tangle-dapp/utils/liquidStaking/timeUnitToMilliseconds.ts index 32544d44a..e9b3d4185 100644 --- a/apps/tangle-dapp/utils/liquidStaking/timeUnitToMilliseconds.ts +++ b/apps/tangle-dapp/utils/liquidStaking/timeUnitToMilliseconds.ts @@ -1,11 +1,11 @@ import { - PARACHAIN_CHAIN_MAP, - ParachainChainId, - SimpleTimeUnitInstance, + LS_CHAIN_MAP, + LsChainId, + LsSimpleParachainTimeUnit, } from '../../constants/liquidStaking'; -const estimateByEra = (chainId: ParachainChainId, era: number): number => { - const chain = PARACHAIN_CHAIN_MAP[chainId]; +const estimateByEra = (chainId: LsChainId, era: number): number => { + const chain = LS_CHAIN_MAP[chainId]; if (chain.substrateTimingSpec === undefined) { throw new Error( @@ -22,8 +22,8 @@ const estimateByEra = (chainId: ParachainChainId, era: number): number => { }; const timeUnitToMilliseconds = ( - chainId: ParachainChainId, - timeUnitInstance: SimpleTimeUnitInstance, + chainId: LsChainId, + timeUnitInstance: LsSimpleParachainTimeUnit, ): number => { switch (timeUnitInstance.unit) { // A conventional hour. From 67e9929f53cf2d133a2bb640c8679730b740a1c9 Mon Sep 17 00:00:00 2001 From: yurixander <101931215+yurixander@users.noreply.github.com> Date: Fri, 9 Aug 2024 18:56:07 -0400 Subject: [PATCH 02/60] feat(tangle-dapp): Add some notes on flow --- ...seLiquifier.ts => useLiquifierBalances.ts} | 7 ++-- .../data/liquifier/useLiquifierDeposit.ts | 34 +++++++++++++++++++ 2 files changed, 36 insertions(+), 5 deletions(-) rename apps/tangle-dapp/data/liquifier/{useLiquifier.ts => useLiquifierBalances.ts} (58%) create mode 100644 apps/tangle-dapp/data/liquifier/useLiquifierDeposit.ts diff --git a/apps/tangle-dapp/data/liquifier/useLiquifier.ts b/apps/tangle-dapp/data/liquifier/useLiquifierBalances.ts similarity index 58% rename from apps/tangle-dapp/data/liquifier/useLiquifier.ts rename to apps/tangle-dapp/data/liquifier/useLiquifierBalances.ts index 8927c9408..fe4243c15 100644 --- a/apps/tangle-dapp/data/liquifier/useLiquifier.ts +++ b/apps/tangle-dapp/data/liquifier/useLiquifierBalances.ts @@ -2,16 +2,13 @@ import { useMemo } from 'react'; import { createPublicClient, http } from 'viem'; import { mainnet } from 'viem/chains'; -const useLiquifier = () => { - // TODO: Generate ABI using Remix, based on Solidity contracts. Then, attach the ABI to the client. +const useLiquifierBalances = () => { const client = useMemo(() => { return createPublicClient({ chain: mainnet, transport: http(), }); }, []); - - // TODO: Implement. }; -export default useLiquifier; +export default useLiquifierBalances; diff --git a/apps/tangle-dapp/data/liquifier/useLiquifierDeposit.ts b/apps/tangle-dapp/data/liquifier/useLiquifierDeposit.ts new file mode 100644 index 000000000..0a59fc924 --- /dev/null +++ b/apps/tangle-dapp/data/liquifier/useLiquifierDeposit.ts @@ -0,0 +1,34 @@ +import { useMemo } from 'react'; +import { createPublicClient, http } from 'viem'; +import { mainnet } from 'viem/chains'; + +/** + * 0. Token.balanceOf(sender) // Obtain the user's token balance (eg. LINK). + * + * 0. Liquifier.balanceOf(sender) // Obtain the user's liquid staking balance (eg. tgLINK). + * + * 1. Token.approve(address(liquifier), depositAmount) // Approve the Liquifier to spend the deposit amount. + * + * 2. Liquifier.deposit(sender, depositAmount) // Deposit the amount to the Liquifier. This is equivalent to calling `stake` or `mint` in Liquid Staking. + * + * Note: The balance of the token is obtained through the token's ERC20 contract. Would need to acquire the contract addresses for each token. + * + * Note: Liquifier.deposit() is invoked through viem.writeContract(), with the address of the Liquifier contract and the deposit amount as arguments. + * + * Note: The liquifier contract's address depends on the selected token. For example, for LINK, the contract address used would be the Chainlink Liquifier Adapter contract. + * + * See more: https://github.com/webb-tools/tnt-core/blob/1f371959884352e7af68e6091c5bb330fcaa58b8/script/XYZ_Stake.s.sol + */ +const useLiquifierDeposit = () => { + // TODO: Generate ABI using Remix, based on Solidity contracts. Then, attach the ABI to the client. + const client = useMemo(() => { + return createPublicClient({ + chain: mainnet, + transport: http(), + }); + }, []); + + // TODO: Implement. +}; + +export default useLiquifierDeposit; From 237d34b6cb3a45cf94b6d6aeaecc62a42756235e Mon Sep 17 00:00:00 2001 From: yurixander <101931215+yurixander@users.noreply.github.com> Date: Mon, 12 Aug 2024 17:18:55 -0400 Subject: [PATCH 03/60] feat(tangle-dapp): Create `useErc20Read` hook --- apps/tangle-dapp/data/liquifier/erc20.ts | 22 +++++++ .../data/liquifier/useErc20Read.ts | 60 +++++++++++++++++++ apps/tangle-dapp/hooks/useNetworkSwitcher.ts | 4 +- 3 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 apps/tangle-dapp/data/liquifier/erc20.ts create mode 100644 apps/tangle-dapp/data/liquifier/useErc20Read.ts diff --git a/apps/tangle-dapp/data/liquifier/erc20.ts b/apps/tangle-dapp/data/liquifier/erc20.ts new file mode 100644 index 000000000..527a3f996 --- /dev/null +++ b/apps/tangle-dapp/data/liquifier/erc20.ts @@ -0,0 +1,22 @@ +import { HexString } from '@polkadot/util/types'; + +export type Erc20TokenDef = { + id: Erc20TokenId; + name: string; + address: HexString; +}; + +export enum Erc20TokenId { + Chainlink, +} + +const ChainlinkTokenDef: Erc20TokenDef = { + id: Erc20TokenId.Chainlink, + name: 'Chainlink', + // TODO: Use Liquifier's testnet address if the environment is development. + address: '0x514910771AF9Ca656af840dff83E8264EcF986CA', +}; + +export const ERC20_TOKEN_MAP: Record = { + [Erc20TokenId.Chainlink]: ChainlinkTokenDef, +}; diff --git a/apps/tangle-dapp/data/liquifier/useErc20Read.ts b/apps/tangle-dapp/data/liquifier/useErc20Read.ts new file mode 100644 index 000000000..cb520584f --- /dev/null +++ b/apps/tangle-dapp/data/liquifier/useErc20Read.ts @@ -0,0 +1,60 @@ +import assert from 'assert'; +import { useCallback, useEffect, useState } from 'react'; +import { + ContractFunctionArgs, + ContractFunctionName, + createPublicClient, + erc20Abi, + http, + PublicClient, +} from 'viem'; +import { mainnet } from 'viem/chains'; + +import { ERC20_TOKEN_MAP, Erc20TokenId } from './erc20'; + +export type Erc20ReadOptions< + T extends ContractFunctionName, +> = { + tokenId: Erc20TokenId; + functionName: T; + args: ContractFunctionArgs; +}; + +const useErc20Read = () => { + const [publicClient, setPublicClient] = useState(null); + + useEffect(() => { + const newPublicClient = createPublicClient({ + chain: mainnet, + transport: http(), + }); + + setPublicClient(newPublicClient); + }, []); + + const read = useCallback( + >( + options: Erc20ReadOptions, + ) => { + assert( + publicClient !== null, + "Should not be able to call this function if the client isn't ready yet", + ); + + const tokenDef = ERC20_TOKEN_MAP[options.tokenId]; + + return publicClient.readContract({ + address: tokenDef.address, + abi: erc20Abi, + functionName: options.functionName, + args: options.args, + }); + }, + [publicClient], + ); + + // Only provide the read function once the client is ready. + return publicClient === null ? null : read; +}; + +export default useErc20Read; diff --git a/apps/tangle-dapp/hooks/useNetworkSwitcher.ts b/apps/tangle-dapp/hooks/useNetworkSwitcher.ts index a2504e266..42c76ce5a 100644 --- a/apps/tangle-dapp/hooks/useNetworkSwitcher.ts +++ b/apps/tangle-dapp/hooks/useNetworkSwitcher.ts @@ -149,7 +149,7 @@ const useNetworkSwitcher = () => { if (activeWallet !== undefined) { try { - const chain = await netWorkToChain(newNetwork, activeWallet); + const chain = await networkToChain(newNetwork, activeWallet); const switchChainResult = await switchChain(chain, activeWallet); @@ -201,7 +201,7 @@ const useNetworkSwitcher = () => { * * @returns the chain */ -async function netWorkToChain(network: Network, activeWallet: WalletConfig) { +async function networkToChain(network: Network, activeWallet: WalletConfig) { if (activeWallet.platform === 'Substrate') { const api = await getApiPromise(network.wsRpcEndpoint); From 474be8b6653f1b719c07b1e8d854a0be45b561ff Mon Sep 17 00:00:00 2001 From: yurixander <101931215+yurixander@users.noreply.github.com> Date: Mon, 12 Aug 2024 21:16:40 -0400 Subject: [PATCH 04/60] feat(tangle-dapp): Create `useLiquifierStake` hook --- .../liquidStaking/useTokenUnlockDurations.ts | 4 +- apps/tangle-dapp/data/liquifier/erc20.ts | 3 + .../data/liquifier/liquifierAbi.ts | 344 ++++++++++++++++++ .../tangle-dapp/data/liquifier/useContract.ts | 112 ++++++ .../data/liquifier/useErc20Read.ts | 60 --- .../liquifier/useEthereumMainnetClient.ts | 20 + .../data/liquifier/useLiquifierDeposit.ts | 34 -- .../data/liquifier/useLiquifierStake.ts | 51 +++ .../hooks/useEvmPrecompileAbiCall.ts | 43 +-- .../utils/liquidStaking/performTimeUnitOp.ts | 21 -- .../utils/liquidStaking/stringifyTimeUnit.ts | 4 +- .../liquidStaking/timeUnitToMilliseconds.ts | 47 --- 12 files changed, 547 insertions(+), 196 deletions(-) create mode 100644 apps/tangle-dapp/data/liquifier/liquifierAbi.ts create mode 100644 apps/tangle-dapp/data/liquifier/useContract.ts delete mode 100644 apps/tangle-dapp/data/liquifier/useErc20Read.ts create mode 100644 apps/tangle-dapp/data/liquifier/useEthereumMainnetClient.ts delete mode 100644 apps/tangle-dapp/data/liquifier/useLiquifierDeposit.ts create mode 100644 apps/tangle-dapp/data/liquifier/useLiquifierStake.ts delete mode 100644 apps/tangle-dapp/utils/liquidStaking/performTimeUnitOp.ts delete mode 100644 apps/tangle-dapp/utils/liquidStaking/timeUnitToMilliseconds.ts diff --git a/apps/tangle-dapp/data/liquidStaking/useTokenUnlockDurations.ts b/apps/tangle-dapp/data/liquidStaking/useTokenUnlockDurations.ts index 14decfa02..91f34a192 100644 --- a/apps/tangle-dapp/data/liquidStaking/useTokenUnlockDurations.ts +++ b/apps/tangle-dapp/data/liquidStaking/useTokenUnlockDurations.ts @@ -6,8 +6,8 @@ import { TANGLE_RESTAKING_PARACHAIN_LOCAL_DEV_NETWORK } from '@webb-tools/webb-u import { useCallback, useMemo } from 'react'; import { + LsSimpleParachainTimeUnit, ParachainCurrency, - SimpleTimeUnitInstance, } from '../../constants/liquidStaking'; import useApiRx from '../../hooks/useApiRx'; import tangleTimeUnitToSimpleInstance from '../../utils/liquidStaking/tangleTimeUnitToSimpleInstance'; @@ -16,7 +16,7 @@ import getValueOfTangleCurrency from './getValueOfTangleCurrency'; export type TokenUnlockDurationEntry = { isNative: boolean; currency: ParachainCurrency; - timeUnit: SimpleTimeUnitInstance; + timeUnit: LsSimpleParachainTimeUnit; }; const useTokenUnlockDurations = () => { diff --git a/apps/tangle-dapp/data/liquifier/erc20.ts b/apps/tangle-dapp/data/liquifier/erc20.ts index 527a3f996..bb3de41c2 100644 --- a/apps/tangle-dapp/data/liquifier/erc20.ts +++ b/apps/tangle-dapp/data/liquifier/erc20.ts @@ -4,6 +4,7 @@ export type Erc20TokenDef = { id: Erc20TokenId; name: string; address: HexString; + liquifierAdapterAddress: HexString; }; export enum Erc20TokenId { @@ -15,6 +16,8 @@ const ChainlinkTokenDef: Erc20TokenDef = { name: 'Chainlink', // TODO: Use Liquifier's testnet address if the environment is development. address: '0x514910771AF9Ca656af840dff83E8264EcF986CA', + // TODO: Use the actual Chainlink Liquifier Adapter address. This is likely deployed to a testnet (Tenderly?). + liquifierAdapterAddress: '0x', }; export const ERC20_TOKEN_MAP: Record = { diff --git a/apps/tangle-dapp/data/liquifier/liquifierAbi.ts b/apps/tangle-dapp/data/liquifier/liquifierAbi.ts new file mode 100644 index 000000000..2a2d43411 --- /dev/null +++ b/apps/tangle-dapp/data/liquifier/liquifierAbi.ts @@ -0,0 +1,344 @@ +const liquifierAbi = [ + { + constant: false, + inputs: [ + { + name: '_operator', + type: 'address', + }, + ], + name: 'addVault', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: true, + inputs: [], + name: 'currentTime', + outputs: [ + { + name: '', + type: 'uint256', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: [], + name: 'getMaxDeposits', + outputs: [ + { + name: '', + type: 'uint256', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: [], + name: 'getMinDeposits', + outputs: [ + { + name: '', + type: 'uint256', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: [], + name: 'getVaultDepositLimits', + outputs: [ + { + name: '', + type: 'uint256', + }, + { + name: '', + type: 'uint256', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: false, + inputs: [ + { + name: '_token', + type: 'address', + }, + { + name: '_stakingPool', + type: 'address', + }, + { + name: '_stakeController', + type: 'address', + }, + { + name: '_vaultImplementation', + type: 'address', + }, + { + name: '_minDepositThreshold', + type: 'uint256', + }, + { + name: '_fees', + type: 'tuple[]', + }, + { + name: '_initialVaults', + type: 'address[]', + }, + ], + name: 'initialize', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: false, + inputs: [ + { + name: '_index', + type: 'uint256', + }, + { + name: '_operator', + type: 'address', + }, + ], + name: 'setOperator', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: false, + inputs: [ + { + name: 'validator', + type: 'address', + }, + { + name: 'amount', + type: 'uint256', + }, + ], + name: 'stake', + outputs: [ + { + name: '', + type: 'uint256', + }, + ], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: false, + inputs: [ + { + name: 'validator', + type: 'address', + }, + { + name: 'currentStake', + type: 'uint256', + }, + ], + name: 'rebase', + outputs: [ + { + name: 'newStake', + type: 'uint256', + }, + ], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: true, + inputs: [ + { + name: 'interfaceId', + type: 'bytes4', + }, + ], + name: 'supportsInterface', + outputs: [ + { + name: '', + type: 'bool', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: [ + { + name: 'validator', + type: 'address', + }, + ], + name: 'isValidator', + outputs: [ + { + name: '', + type: 'bool', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: [ + { + name: 'validator', + type: 'address', + }, + { + name: 'assets', + type: 'uint256', + }, + ], + name: 'previewDeposit', + outputs: [ + { + name: '', + type: 'uint256', + }, + ], + payable: false, + stateMutability: 'pure', + type: 'function', + }, + { + constant: true, + inputs: [ + { + name: 'validator', + type: 'uint256', + }, + ], + name: 'previewWithdraw', + outputs: [ + { + name: 'amount', + type: 'uint256', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: [], + name: 'unlockTime', + outputs: [ + { + name: '', + type: 'uint256', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: false, + inputs: [ + { + name: 'validator', + type: 'address', + }, + { + name: 'amount', + type: 'uint256', + }, + ], + name: 'unstake', + outputs: [ + { + name: '', + type: 'uint256', + }, + ], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: false, + inputs: [ + { + name: 'validator', + type: 'address', + }, + { + name: 'amount', + type: 'uint256', + }, + ], + name: 'withdraw', + outputs: [ + { + name: 'amount', + type: 'uint256', + }, + ], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + name: 'operator', + type: 'address', + }, + ], + name: 'VaultAdded', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + name: 'depositedAmount', + type: 'uint256', + }, + ], + name: 'DepositBufferedTokens', + type: 'event', + }, +] as const; + +export default liquifierAbi; diff --git a/apps/tangle-dapp/data/liquifier/useContract.ts b/apps/tangle-dapp/data/liquifier/useContract.ts new file mode 100644 index 000000000..db3234c8c --- /dev/null +++ b/apps/tangle-dapp/data/liquifier/useContract.ts @@ -0,0 +1,112 @@ +import { HexString } from '@polkadot/util/types'; +import assert from 'assert'; +import { useCallback } from 'react'; +import { + Abi as ViemAbi, + ContractFunctionArgs, + ContractFunctionName, +} from 'viem'; +import { + simulateContract, + waitForTransactionReceipt, + writeContract, +} from 'viem/actions'; +import { mainnet } from 'viem/chains'; +import { useConnectorClient } from 'wagmi'; + +import useEvmAddress20 from '../../hooks/useEvmAddress'; +import useEthereumMainnetClient from './useEthereumMainnetClient'; + +export type ContractReadOptions< + Abi extends ViemAbi, + FunctionName extends ContractFunctionName, +> = { + address: HexString; + functionName: FunctionName; + args: ContractFunctionArgs; +}; + +export type ContractWriteOptions< + Abi extends ViemAbi, + FunctionName extends ContractFunctionName, +> = { + address: HexString; + functionName: FunctionName; + args: ContractFunctionArgs; +}; + +const useContract = (abi: Abi) => { + const publicClient = useEthereumMainnetClient(); + const { data: connectorClient } = useConnectorClient(); + const activeEvmAddress20 = useEvmAddress20(); + + const read = useCallback( + >( + options: ContractReadOptions, + ) => { + assert( + publicClient !== null, + "Should not be able to call this function if the client isn't ready yet", + ); + + return publicClient.readContract({ + address: options.address, + abi, + functionName: options.functionName, + args: options.args, + }); + }, + [abi, publicClient], + ); + + const write = useCallback( + async < + FunctionName extends ContractFunctionName, + >( + options: ContractWriteOptions, + ) => { + assert( + connectorClient !== undefined, + "Should not be able to call this function if the client isn't ready yet", + ); + + assert( + activeEvmAddress20 !== null, + 'Should not be able to call this function if there is no active EVM account', + ); + + // TODO: Handle possible errors thrown by `simulateContract`. + const { request } = await simulateContract(connectorClient, { + chain: mainnet, + address: options.address, + functionName: options.functionName, + account: activeEvmAddress20, + // TODO: Getting the type of `args` and `abi` right has proven quite difficult. + abi: abi as any, + args: options.args as any, + }); + + const txHash = await writeContract(connectorClient, request); + + // Return the transaction receipt, which contains the transaction status. + return waitForTransactionReceipt(connectorClient, { + hash: txHash, + // TODO: Make use of the `timeout` parameter, and error handle if it fails due to timeout. + }); + }, + [abi, activeEvmAddress20, connectorClient], + ); + + return { + // Only provide the read functions once the public client is ready. + read: publicClient === null ? null : read, + // Only provide the write function once the connector client is ready, + // and there is an active EVM account. + write: + connectorClient === undefined || activeEvmAddress20 === null + ? null + : write, + }; +}; + +export default useContract; diff --git a/apps/tangle-dapp/data/liquifier/useErc20Read.ts b/apps/tangle-dapp/data/liquifier/useErc20Read.ts deleted file mode 100644 index cb520584f..000000000 --- a/apps/tangle-dapp/data/liquifier/useErc20Read.ts +++ /dev/null @@ -1,60 +0,0 @@ -import assert from 'assert'; -import { useCallback, useEffect, useState } from 'react'; -import { - ContractFunctionArgs, - ContractFunctionName, - createPublicClient, - erc20Abi, - http, - PublicClient, -} from 'viem'; -import { mainnet } from 'viem/chains'; - -import { ERC20_TOKEN_MAP, Erc20TokenId } from './erc20'; - -export type Erc20ReadOptions< - T extends ContractFunctionName, -> = { - tokenId: Erc20TokenId; - functionName: T; - args: ContractFunctionArgs; -}; - -const useErc20Read = () => { - const [publicClient, setPublicClient] = useState(null); - - useEffect(() => { - const newPublicClient = createPublicClient({ - chain: mainnet, - transport: http(), - }); - - setPublicClient(newPublicClient); - }, []); - - const read = useCallback( - >( - options: Erc20ReadOptions, - ) => { - assert( - publicClient !== null, - "Should not be able to call this function if the client isn't ready yet", - ); - - const tokenDef = ERC20_TOKEN_MAP[options.tokenId]; - - return publicClient.readContract({ - address: tokenDef.address, - abi: erc20Abi, - functionName: options.functionName, - args: options.args, - }); - }, - [publicClient], - ); - - // Only provide the read function once the client is ready. - return publicClient === null ? null : read; -}; - -export default useErc20Read; diff --git a/apps/tangle-dapp/data/liquifier/useEthereumMainnetClient.ts b/apps/tangle-dapp/data/liquifier/useEthereumMainnetClient.ts new file mode 100644 index 000000000..03eadad41 --- /dev/null +++ b/apps/tangle-dapp/data/liquifier/useEthereumMainnetClient.ts @@ -0,0 +1,20 @@ +import { useEffect, useState } from 'react'; +import { createPublicClient, http, PublicClient } from 'viem'; +import { mainnet } from 'viem/chains'; + +const useEthereumMainnetClient = () => { + const [publicClient, setPublicClient] = useState(null); + + useEffect(() => { + const newPublicClient = createPublicClient({ + chain: mainnet, + transport: http(), + }); + + setPublicClient(newPublicClient); + }, []); + + return publicClient; +}; + +export default useEthereumMainnetClient; diff --git a/apps/tangle-dapp/data/liquifier/useLiquifierDeposit.ts b/apps/tangle-dapp/data/liquifier/useLiquifierDeposit.ts deleted file mode 100644 index 0a59fc924..000000000 --- a/apps/tangle-dapp/data/liquifier/useLiquifierDeposit.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { useMemo } from 'react'; -import { createPublicClient, http } from 'viem'; -import { mainnet } from 'viem/chains'; - -/** - * 0. Token.balanceOf(sender) // Obtain the user's token balance (eg. LINK). - * - * 0. Liquifier.balanceOf(sender) // Obtain the user's liquid staking balance (eg. tgLINK). - * - * 1. Token.approve(address(liquifier), depositAmount) // Approve the Liquifier to spend the deposit amount. - * - * 2. Liquifier.deposit(sender, depositAmount) // Deposit the amount to the Liquifier. This is equivalent to calling `stake` or `mint` in Liquid Staking. - * - * Note: The balance of the token is obtained through the token's ERC20 contract. Would need to acquire the contract addresses for each token. - * - * Note: Liquifier.deposit() is invoked through viem.writeContract(), with the address of the Liquifier contract and the deposit amount as arguments. - * - * Note: The liquifier contract's address depends on the selected token. For example, for LINK, the contract address used would be the Chainlink Liquifier Adapter contract. - * - * See more: https://github.com/webb-tools/tnt-core/blob/1f371959884352e7af68e6091c5bb330fcaa58b8/script/XYZ_Stake.s.sol - */ -const useLiquifierDeposit = () => { - // TODO: Generate ABI using Remix, based on Solidity contracts. Then, attach the ABI to the client. - const client = useMemo(() => { - return createPublicClient({ - chain: mainnet, - transport: http(), - }); - }, []); - - // TODO: Implement. -}; - -export default useLiquifierDeposit; diff --git a/apps/tangle-dapp/data/liquifier/useLiquifierStake.ts b/apps/tangle-dapp/data/liquifier/useLiquifierStake.ts new file mode 100644 index 000000000..0b96ef180 --- /dev/null +++ b/apps/tangle-dapp/data/liquifier/useLiquifierStake.ts @@ -0,0 +1,51 @@ +import { BN } from '@polkadot/util'; +import assert from 'assert'; + +import { ERC20_TOKEN_MAP, Erc20TokenId } from './erc20'; +import liquifierAbi from './liquifierAbi'; +import useContract from './useContract'; + +/** + * 0. Token.balanceOf(sender) // Obtain the user's token balance (eg. LINK). + * + * 0. Liquifier.balanceOf(sender) // Obtain the user's liquid staking balance (eg. tgLINK). + * + * 1. Token.approve(address(liquifier), depositAmount) // Approve the Liquifier to spend the deposit amount. + * + * 2. Liquifier.deposit(sender, depositAmount) // Deposit the amount to the Liquifier. This is equivalent to calling `stake` or `mint` in Liquid Staking. + * + * Note: The balance of the token is obtained through the token's ERC20 contract. Would need to acquire the contract addresses for each token. + * + * Note: Liquifier.deposit() is invoked through viem.writeContract(), with the address of the Liquifier contract and the deposit amount as arguments. + * + * Note: The liquifier contract's address depends on the selected token. For example, for LINK, the contract address used would be the Chainlink Liquifier Adapter contract. + * + * See more: https://github.com/webb-tools/tnt-core/blob/1f371959884352e7af68e6091c5bb330fcaa58b8/script/XYZ_Stake.s.sol + */ +const useLiquifierStake = () => { + const { write } = useContract(liquifierAbi); + + // TODO: Return proxy read & write functions, each accepting an Erc20TokenId, and using `ERC20_TOKEN_MAP` to get the token definition, and from there get the actual address of the Liquifier's adapter contract for the given token. + + const stake = async (token: Erc20TokenId, amount: BN) => { + // TODO: Need to call `Token.approve` before calling `Liquifier.stake`. This is not implemented yet. Also, should the user balance check be done here or assume that the consumer of the hook will handle that? + + assert( + write !== null, + 'Should not be able to call this function if the client is not ready yet', + ); + + const tokenDef = ERC20_TOKEN_MAP[token]; + + return write({ + address: tokenDef.liquifierAdapterAddress, + functionName: 'stake', + // TODO: Provide the first arg. (validator). Need to figure out how it works on Chainlink (vaults? single address?). See: https://github.com/webb-tools/tnt-core/blob/21c158d6cb11e2b5f50409d377431e7cd51ff72f/src/lst/adapters/ChainlinkAdapter.sol#L187 + args: ['0x0', amount], + }); + }; + + return write === null ? null : stake; +}; + +export default useLiquifierStake; diff --git a/apps/tangle-dapp/hooks/useEvmPrecompileAbiCall.ts b/apps/tangle-dapp/hooks/useEvmPrecompileAbiCall.ts index bc0137738..16c047c45 100644 --- a/apps/tangle-dapp/hooks/useEvmPrecompileAbiCall.ts +++ b/apps/tangle-dapp/hooks/useEvmPrecompileAbiCall.ts @@ -21,23 +21,6 @@ import ensureError from '../utils/ensureError'; import useEvmAddress20 from './useEvmAddress'; import { TxStatus } from './useSubstrateTx'; -// TODO: For some reason, Viem returns `any` for the tx receipt. Perhaps it is because it has no knowledge of the network, and thus no knowledge of its produced tx receipts. As a temporary workaround, use this custom type. -type TxReceipt = { - transactionHash: AddressType; - transactionIndex: number; - blockHash: AddressType; - from: AddressType; - to: AddressType | null; - blockNumber: bigint; - cumulativeGasUsed: bigint; - gasUsed: bigint; - logs: unknown[]; - logsBloom: string; - status: 'success' | 'reverted'; - type: string; - contractAddress: unknown | null; -}; - export type AbiCallArg = string | number | BN | boolean; export type AbiEncodeableValue = string | number | boolean | bigint; @@ -86,7 +69,7 @@ function useEvmPrecompileAbiCall< const [txHash, setTxHash] = useState(null); const [successMessage, setSuccessMessage] = useState(null); - const activeEvmAddress = useEvmAddress20(); + const activeEvmAddress20 = useEvmAddress20(); const { data: connectorClient } = useConnectorClient(); // Useful for debugging. @@ -99,7 +82,7 @@ function useEvmPrecompileAbiCall< const execute = useCallback( async (context: Context) => { if ( - activeEvmAddress === null || + activeEvmAddress20 === null || status === TxStatus.PROCESSING || connectorClient === undefined ) { @@ -125,22 +108,22 @@ function useEvmPrecompileAbiCall< abi: getPrecompileAbi(precompile), functionName: factoryResult.functionName, args: factoryResult.arguments, - account: activeEvmAddress, + account: activeEvmAddress20, }); const newTxHash = await writeContract(connectorClient, request); setTxHash(newTxHash); - const txReceipt: TxReceipt = await waitForTransactionReceipt( - connectorClient, - { - hash: newTxHash, - // TODO: Make use of the `timeout` parameter, and error handle if it fails due to timeout. - }, - ); + const txReceipt = await waitForTransactionReceipt(connectorClient, { + hash: newTxHash, + // TODO: Make use of the `timeout` parameter, and error handle if it fails due to timeout. + }); - console.debug('EVM transaction receipt:', txReceipt); + console.debug( + 'EVM pre-compile ABI call transaction receipt:', + txReceipt, + ); setStatus( txReceipt.status === 'success' ? TxStatus.COMPLETE : TxStatus.ERROR, @@ -161,7 +144,7 @@ function useEvmPrecompileAbiCall< } }, // prettier-ignore - [activeEvmAddress, status, connectorClient, factory, precompile, getSuccessMessageFnc], + [activeEvmAddress20, status, connectorClient, factory, precompile, getSuccessMessageFnc], ); const reset = useCallback(() => { @@ -172,7 +155,7 @@ function useEvmPrecompileAbiCall< // Prevent the consumer from executing the call if the active // account is not an EVM account. return { - execute: activeEvmAddress !== null ? execute : null, + execute: activeEvmAddress20 !== null ? execute : null, reset, status, error, diff --git a/apps/tangle-dapp/utils/liquidStaking/performTimeUnitOp.ts b/apps/tangle-dapp/utils/liquidStaking/performTimeUnitOp.ts deleted file mode 100644 index cffb8b563..000000000 --- a/apps/tangle-dapp/utils/liquidStaking/performTimeUnitOp.ts +++ /dev/null @@ -1,21 +0,0 @@ -import assert from 'assert'; - -import { LsSimpleParachainTimeUnit } from '../../constants/liquidStaking'; - -const performTimeUnitOp = ( - operation: (a: number, b: number) => number, - a: LsSimpleParachainTimeUnit, - b: LsSimpleParachainTimeUnit, -): LsSimpleParachainTimeUnit => { - assert( - a.unit === b.unit, - `Time units must be of the same type, otherwise operations are not possible; Received: ${a.unit} and ${b.unit}`, - ); - - return { - unit: a.unit, - value: operation(a.value, b.value), - }; -}; - -export default performTimeUnitOp; diff --git a/apps/tangle-dapp/utils/liquidStaking/stringifyTimeUnit.ts b/apps/tangle-dapp/utils/liquidStaking/stringifyTimeUnit.ts index f1efd203c..d9bf52cf8 100644 --- a/apps/tangle-dapp/utils/liquidStaking/stringifyTimeUnit.ts +++ b/apps/tangle-dapp/utils/liquidStaking/stringifyTimeUnit.ts @@ -1,7 +1,7 @@ -import { SimpleTimeUnitInstance } from '../../constants/liquidStaking'; +import { LsSimpleParachainTimeUnit } from '../../constants/liquidStaking'; const stringifyTimeUnit = ( - timeUnit: SimpleTimeUnitInstance, + timeUnit: LsSimpleParachainTimeUnit, ): [number, string] => { const plurality = timeUnit.value === 1 ? '' : 's'; diff --git a/apps/tangle-dapp/utils/liquidStaking/timeUnitToMilliseconds.ts b/apps/tangle-dapp/utils/liquidStaking/timeUnitToMilliseconds.ts deleted file mode 100644 index e9b3d4185..000000000 --- a/apps/tangle-dapp/utils/liquidStaking/timeUnitToMilliseconds.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { - LS_CHAIN_MAP, - LsChainId, - LsSimpleParachainTimeUnit, -} from '../../constants/liquidStaking'; - -const estimateByEra = (chainId: LsChainId, era: number): number => { - const chain = LS_CHAIN_MAP[chainId]; - - if (chain.substrateTimingSpec === undefined) { - throw new Error( - `Chain ${chain.name} does not have a Substrate timing specification`, - ); - } - - return ( - chain.substrateTimingSpec.expectedBlockTimeMs * - chain.substrateTimingSpec.blocksPerSession * - chain.substrateTimingSpec.sessionsPerEra * - era - ); -}; - -const timeUnitToMilliseconds = ( - chainId: LsChainId, - timeUnitInstance: LsSimpleParachainTimeUnit, -): number => { - switch (timeUnitInstance.unit) { - // A conventional hour. - case 'Hour': { - return timeUnitInstance.value * 60 * 60 * 1000; - } - // The duration of an era is chain-specific, so the timing spec - // is required. - case 'Era': { - return estimateByEra(chainId, timeUnitInstance.value); - } - default: { - // TODO: Add support for the remaining time unit types. - throw new Error( - `Time unit type not yet supported: ${timeUnitInstance.unit}`, - ); - } - } -}; - -export default timeUnitToMilliseconds; From f8a97c7a2f54865485e76b959ee5daf8c2303b79 Mon Sep 17 00:00:00 2001 From: yurixander <101931215+yurixander@users.noreply.github.com> Date: Mon, 12 Aug 2024 23:22:59 -0400 Subject: [PATCH 05/60] feat(tangle-dapp): Continue implementation of `useLiquifierDeposit` --- apps/tangle-dapp/constants/index.ts | 2 + ...Abi.ts => liquifierChainlinkAdapterAbi.ts} | 4 +- .../data/liquifier/useLiquifierDeposit.ts | 103 ++++++++++++++++++ .../data/liquifier/useLiquifierStake.ts | 51 --------- apps/tangle-dapp/hooks/useTxNotification.tsx | 8 +- 5 files changed, 113 insertions(+), 55 deletions(-) rename apps/tangle-dapp/data/liquifier/{liquifierAbi.ts => liquifierChainlinkAdapterAbi.ts} (98%) create mode 100644 apps/tangle-dapp/data/liquifier/useLiquifierDeposit.ts delete mode 100644 apps/tangle-dapp/data/liquifier/useLiquifierStake.ts diff --git a/apps/tangle-dapp/constants/index.ts b/apps/tangle-dapp/constants/index.ts index ce9dc8c30..21883c840 100644 --- a/apps/tangle-dapp/constants/index.ts +++ b/apps/tangle-dapp/constants/index.ts @@ -63,6 +63,8 @@ export enum TxName { LST_REDEEM = 'redeem', LST_REBOND = 'cancel unstake request', LST_WITHDRAW_REDEEM = 'withdraw redeemed tokens', + LST_LIQUIFIER_APPROVE = 'liquifier approve spending', + LST_LIQUIFIER_DEPOSIT = 'liquifier deposit', } export const PAYMENT_DESTINATION_OPTIONS: StakingRewardsDestinationDisplayText[] = diff --git a/apps/tangle-dapp/data/liquifier/liquifierAbi.ts b/apps/tangle-dapp/data/liquifier/liquifierChainlinkAdapterAbi.ts similarity index 98% rename from apps/tangle-dapp/data/liquifier/liquifierAbi.ts rename to apps/tangle-dapp/data/liquifier/liquifierChainlinkAdapterAbi.ts index 2a2d43411..bc2c610fb 100644 --- a/apps/tangle-dapp/data/liquifier/liquifierAbi.ts +++ b/apps/tangle-dapp/data/liquifier/liquifierChainlinkAdapterAbi.ts @@ -1,4 +1,4 @@ -const liquifierAbi = [ +const liquifierChainlinkAdapterAbi = [ { constant: false, inputs: [ @@ -341,4 +341,4 @@ const liquifierAbi = [ }, ] as const; -export default liquifierAbi; +export default liquifierChainlinkAdapterAbi; diff --git a/apps/tangle-dapp/data/liquifier/useLiquifierDeposit.ts b/apps/tangle-dapp/data/liquifier/useLiquifierDeposit.ts new file mode 100644 index 000000000..f19f832da --- /dev/null +++ b/apps/tangle-dapp/data/liquifier/useLiquifierDeposit.ts @@ -0,0 +1,103 @@ +import { BN } from '@polkadot/util'; +import assert from 'assert'; +import { erc20Abi } from 'viem'; + +import { TxName } from '../../constants'; +import useTxNotification from '../../hooks/useTxNotification'; +import { ERC20_TOKEN_MAP, Erc20TokenId } from './erc20'; +import liquifierChainlinkAdapterAbi from './liquifierChainlinkAdapterAbi'; +import useContract from './useContract'; + +/** + * 0. Token.balanceOf(sender) // Obtain the user's token balance (eg. LINK). + * + * 0. Liquifier.balanceOf(sender) // Obtain the user's liquid staking balance (eg. tgLINK). + * + * 1. Token.approve(address(liquifier), depositAmount) // Approve the Liquifier to spend the deposit amount. + * + * 2. Liquifier.deposit(sender, depositAmount) // Deposit the amount to the Liquifier. This is equivalent to calling `stake` or `mint` in Liquid Staking. + * + * Note: The balance of the token is obtained through the token's ERC20 contract. Would need to acquire the contract addresses for each token. + * + * Note: Liquifier.deposit() is invoked through viem.writeContract(), with the address of the Liquifier contract and the deposit amount as arguments. + * + * Note: The liquifier contract's address depends on the selected token. For example, for LINK, the contract address used would be the Chainlink Liquifier Adapter contract. + * + * See more: https://github.com/webb-tools/tnt-core/blob/1f371959884352e7af68e6091c5bb330fcaa58b8/script/XYZ_Stake.s.sol + */ +const useLiquifierDeposit = () => { + const { write: writeChainlinkErc20 } = useContract(erc20Abi); + const { write } = useContract(liquifierChainlinkAdapterAbi); + + const { + notifyProcessing: notifyApproveProcessing, + notifySuccess: notifyApproveSuccess, + notifyError: notifyApproveError, + } = useTxNotification(TxName.LST_LIQUIFIER_APPROVE); + + const { + notifyProcessing: notifyDepositProcessing, + notifySuccess: notifyDepositSuccess, + notifyError: notifyDepositError, + } = useTxNotification(TxName.LST_LIQUIFIER_DEPOSIT); + + // TODO: Return proxy read & write functions, each accepting an Erc20TokenId, and using `ERC20_TOKEN_MAP` to get the token definition, and from there get the actual address of the Liquifier's adapter contract for the given token. + + const isReady = write !== null && writeChainlinkErc20 !== null; + + const deposit = async (token: Erc20TokenId, amount: BN) => { + // TODO: Need to call `Token.approve` before calling `Liquifier.stake`. This is not implemented yet. Also, should the user balance check be done here or assume that the consumer of the hook will handle that? + + assert( + isReady, + 'Should not be able to call this function if the requirements are not ready yet', + ); + + const tokenDef = ERC20_TOKEN_MAP[token]; + + notifyApproveProcessing(); + + // Approve spending the token amount by the Liquifier contract. + const approveTxReceipt = await writeChainlinkErc20({ + address: tokenDef.address, + functionName: 'approve', + args: [tokenDef.liquifierAdapterAddress, BigInt(amount.toString())], + }); + + if (approveTxReceipt.status === 'reverted') { + notifyApproveError( + 'Failed to approve spending the token amount by the Liquifier contract', + ); + + return false; + } + + // TODO: Ensure that the transaction hash takes to the proper explorer URL, since it is not a Substrate transaction. + notifyApproveSuccess(approveTxReceipt.transactionHash); + notifyDepositProcessing(); + + const depositTxReceipt = await write({ + address: tokenDef.liquifierAdapterAddress, + // TODO: This should be calling `deposit`, not `stake`. See: https://github.com/webb-tools/tnt-core/blob/1f371959884352e7af68e6091c5bb330fcaa58b8/script/XYZ_Stake.s.sol#L41 + functionName: 'stake', + // TODO: Provide the first arg. (validator). Need to figure out how it works on Chainlink (vaults? single address?). See: https://github.com/webb-tools/tnt-core/blob/21c158d6cb11e2b5f50409d377431e7cd51ff72f/src/lst/adapters/ChainlinkAdapter.sol#L187 + args: ['0x0', BigInt(amount.toString())], + }); + + if (depositTxReceipt.status === 'reverted') { + notifyDepositError('Failed to deposit the token amount to the Liquifier'); + + return false; + } + + notifyDepositSuccess(depositTxReceipt.transactionHash); + + return true; + }; + + // Wait for the requirements to be ready before + // returning the deposit function. + return isReady ? null : deposit; +}; + +export default useLiquifierDeposit; diff --git a/apps/tangle-dapp/data/liquifier/useLiquifierStake.ts b/apps/tangle-dapp/data/liquifier/useLiquifierStake.ts deleted file mode 100644 index 0b96ef180..000000000 --- a/apps/tangle-dapp/data/liquifier/useLiquifierStake.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { BN } from '@polkadot/util'; -import assert from 'assert'; - -import { ERC20_TOKEN_MAP, Erc20TokenId } from './erc20'; -import liquifierAbi from './liquifierAbi'; -import useContract from './useContract'; - -/** - * 0. Token.balanceOf(sender) // Obtain the user's token balance (eg. LINK). - * - * 0. Liquifier.balanceOf(sender) // Obtain the user's liquid staking balance (eg. tgLINK). - * - * 1. Token.approve(address(liquifier), depositAmount) // Approve the Liquifier to spend the deposit amount. - * - * 2. Liquifier.deposit(sender, depositAmount) // Deposit the amount to the Liquifier. This is equivalent to calling `stake` or `mint` in Liquid Staking. - * - * Note: The balance of the token is obtained through the token's ERC20 contract. Would need to acquire the contract addresses for each token. - * - * Note: Liquifier.deposit() is invoked through viem.writeContract(), with the address of the Liquifier contract and the deposit amount as arguments. - * - * Note: The liquifier contract's address depends on the selected token. For example, for LINK, the contract address used would be the Chainlink Liquifier Adapter contract. - * - * See more: https://github.com/webb-tools/tnt-core/blob/1f371959884352e7af68e6091c5bb330fcaa58b8/script/XYZ_Stake.s.sol - */ -const useLiquifierStake = () => { - const { write } = useContract(liquifierAbi); - - // TODO: Return proxy read & write functions, each accepting an Erc20TokenId, and using `ERC20_TOKEN_MAP` to get the token definition, and from there get the actual address of the Liquifier's adapter contract for the given token. - - const stake = async (token: Erc20TokenId, amount: BN) => { - // TODO: Need to call `Token.approve` before calling `Liquifier.stake`. This is not implemented yet. Also, should the user balance check be done here or assume that the consumer of the hook will handle that? - - assert( - write !== null, - 'Should not be able to call this function if the client is not ready yet', - ); - - const tokenDef = ERC20_TOKEN_MAP[token]; - - return write({ - address: tokenDef.liquifierAdapterAddress, - functionName: 'stake', - // TODO: Provide the first arg. (validator). Need to figure out how it works on Chainlink (vaults? single address?). See: https://github.com/webb-tools/tnt-core/blob/21c158d6cb11e2b5f50409d377431e7cd51ff72f/src/lst/adapters/ChainlinkAdapter.sol#L187 - args: ['0x0', amount], - }); - }; - - return write === null ? null : stake; -}; - -export default useLiquifierStake; diff --git a/apps/tangle-dapp/hooks/useTxNotification.tsx b/apps/tangle-dapp/hooks/useTxNotification.tsx index 18d2317c1..bee0b438b 100644 --- a/apps/tangle-dapp/hooks/useTxNotification.tsx +++ b/apps/tangle-dapp/hooks/useTxNotification.tsx @@ -32,6 +32,8 @@ const SUCCESS_MESSAGES: Record = { [TxName.LST_REDEEM]: 'Redeem request submitted', [TxName.LST_REBOND]: 'Unstake request cancelled', [TxName.LST_WITHDRAW_REDEEM]: 'Unstake request executed', + [TxName.LST_LIQUIFIER_DEPOSIT]: 'Liquifier deposit successful', + [TxName.LST_LIQUIFIER_APPROVE]: 'Liquifier approval successful', }; // TODO: Use a ref for the key to permit multiple rapid fire transactions from stacking under the same key. Otherwise, use a global state counter via Zustand. @@ -97,14 +99,16 @@ const useTxNotification = (txName: TxName, explorerUrl?: string) => { ); const notifyError = useCallback( - (error: Error) => { + (error: Error | string) => { closeSnackbar(processingKey); + const errorMessage = typeof error === 'string' ? error : error.message; + enqueueSnackbar(
    {capitalize(txName)} failed - {error.message} + {errorMessage}
    , { variant: 'error', From 3f61d65670a0140861c687aa26b4f86d01595e61 Mon Sep 17 00:00:00 2001 From: yurixander <101931215+yurixander@users.noreply.github.com> Date: Mon, 12 Aug 2024 23:35:41 -0400 Subject: [PATCH 06/60] feat(tangle-dapp): Add liquifier ABI --- .../liquifier/{erc20.ts => erc20Tokens.ts} | 0 .../data/liquifier/liquifierAbi.ts | 188 ++++++++++++++++++ .../data/liquifier/useLiquifierDeposit.ts | 25 ++- apps/tangle-dapp/hooks/useTxNotification.tsx | 2 + 4 files changed, 204 insertions(+), 11 deletions(-) rename apps/tangle-dapp/data/liquifier/{erc20.ts => erc20Tokens.ts} (100%) create mode 100644 apps/tangle-dapp/data/liquifier/liquifierAbi.ts diff --git a/apps/tangle-dapp/data/liquifier/erc20.ts b/apps/tangle-dapp/data/liquifier/erc20Tokens.ts similarity index 100% rename from apps/tangle-dapp/data/liquifier/erc20.ts rename to apps/tangle-dapp/data/liquifier/erc20Tokens.ts diff --git a/apps/tangle-dapp/data/liquifier/liquifierAbi.ts b/apps/tangle-dapp/data/liquifier/liquifierAbi.ts new file mode 100644 index 000000000..7dd50b532 --- /dev/null +++ b/apps/tangle-dapp/data/liquifier/liquifierAbi.ts @@ -0,0 +1,188 @@ +const liquifierAbi = [ + { + type: 'constructor', + stateMutability: 'nonpayable', + inputs: [ + { internalType: 'address', name: '_registry', type: 'address' }, + { internalType: 'address', name: '_unlocks', type: 'address' }, + ], + }, + { + type: 'function', + name: 'deposit', + inputs: [ + { internalType: 'address', name: 'receiver', type: 'address' }, + { internalType: 'uint256', name: 'assets', type: 'uint256' }, + ], + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'unlock', + inputs: [{ internalType: 'uint256', name: 'assets', type: 'uint256' }], + outputs: [{ internalType: 'uint256', name: 'unlockID', type: 'uint256' }], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'withdraw', + inputs: [ + { internalType: 'address', name: 'receiver', type: 'address' }, + { internalType: 'uint256', name: 'unlockID', type: 'uint256' }, + ], + outputs: [{ internalType: 'uint256', name: 'amount', type: 'uint256' }], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'rebase', + inputs: [], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'name', + inputs: [], + outputs: [{ internalType: 'string', name: '', type: 'string' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'symbol', + inputs: [], + outputs: [{ internalType: 'string', name: '', type: 'string' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'transfer', + inputs: [ + { internalType: 'address', name: 'to', type: 'address' }, + { internalType: 'uint256', name: 'amount', type: 'uint256' }, + ], + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'transferFrom', + inputs: [ + { internalType: 'address', name: 'from', type: 'address' }, + { internalType: 'address', name: 'to', type: 'address' }, + { internalType: 'uint256', name: 'amount', type: 'uint256' }, + ], + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'previewDeposit', + inputs: [{ internalType: 'uint256', name: 'assets', type: 'uint256' }], + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'previewWithdraw', + inputs: [{ internalType: 'uint256', name: 'unlockID', type: 'uint256' }], + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'unlockMaturity', + inputs: [{ internalType: 'uint256', name: 'unlockID', type: 'uint256' }], + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + }, + { + type: 'event', + name: 'Deposit', + inputs: [ + { + indexed: false, + internalType: 'address', + name: 'from', + type: 'address', + }, + { indexed: false, internalType: 'address', name: 'to', type: 'address' }, + { + indexed: false, + internalType: 'uint256', + name: 'assets', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'tgTokenOut', + type: 'uint256', + }, + ], + }, + { + type: 'event', + name: 'Unlock', + inputs: [ + { + indexed: false, + internalType: 'address', + name: 'from', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'assets', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'unlockID', + type: 'uint256', + }, + ], + }, + { + type: 'event', + name: 'Withdraw', + inputs: [ + { indexed: false, internalType: 'address', name: 'to', type: 'address' }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'unlockID', + type: 'uint256', + }, + ], + }, + { + type: 'event', + name: 'Rebase', + inputs: [ + { + indexed: false, + internalType: 'uint256', + name: 'currentStake', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'newStake', + type: 'uint256', + }, + ], + }, +] as const; + +export default liquifierAbi; diff --git a/apps/tangle-dapp/data/liquifier/useLiquifierDeposit.ts b/apps/tangle-dapp/data/liquifier/useLiquifierDeposit.ts index f19f832da..66199ee80 100644 --- a/apps/tangle-dapp/data/liquifier/useLiquifierDeposit.ts +++ b/apps/tangle-dapp/data/liquifier/useLiquifierDeposit.ts @@ -3,9 +3,10 @@ import assert from 'assert'; import { erc20Abi } from 'viem'; import { TxName } from '../../constants'; +import useEvmAddress20 from '../../hooks/useEvmAddress'; import useTxNotification from '../../hooks/useTxNotification'; -import { ERC20_TOKEN_MAP, Erc20TokenId } from './erc20'; -import liquifierChainlinkAdapterAbi from './liquifierChainlinkAdapterAbi'; +import { ERC20_TOKEN_MAP, Erc20TokenId } from './erc20Tokens'; +import liquifierAbi from './liquifierAbi'; import useContract from './useContract'; /** @@ -26,8 +27,9 @@ import useContract from './useContract'; * See more: https://github.com/webb-tools/tnt-core/blob/1f371959884352e7af68e6091c5bb330fcaa58b8/script/XYZ_Stake.s.sol */ const useLiquifierDeposit = () => { + const activeEvmAddress20 = useEvmAddress20(); const { write: writeChainlinkErc20 } = useContract(erc20Abi); - const { write } = useContract(liquifierChainlinkAdapterAbi); + const { write: writeLiquifier } = useContract(liquifierAbi); const { notifyProcessing: notifyApproveProcessing, @@ -41,9 +43,10 @@ const useLiquifierDeposit = () => { notifyError: notifyDepositError, } = useTxNotification(TxName.LST_LIQUIFIER_DEPOSIT); - // TODO: Return proxy read & write functions, each accepting an Erc20TokenId, and using `ERC20_TOKEN_MAP` to get the token definition, and from there get the actual address of the Liquifier's adapter contract for the given token. - - const isReady = write !== null && writeChainlinkErc20 !== null; + const isReady = + writeLiquifier !== null && + writeChainlinkErc20 !== null && + activeEvmAddress20 !== null; const deposit = async (token: Erc20TokenId, amount: BN) => { // TODO: Need to call `Token.approve` before calling `Liquifier.stake`. This is not implemented yet. Also, should the user balance check be done here or assume that the consumer of the hook will handle that? @@ -76,12 +79,12 @@ const useLiquifierDeposit = () => { notifyApproveSuccess(approveTxReceipt.transactionHash); notifyDepositProcessing(); - const depositTxReceipt = await write({ + const depositTxReceipt = await writeLiquifier({ + // TODO: Does the adapter contract have a deposit function? It doesn't seem like so. In that case, will need to update the way that Liquifier contract's address is handled. address: tokenDef.liquifierAdapterAddress, - // TODO: This should be calling `deposit`, not `stake`. See: https://github.com/webb-tools/tnt-core/blob/1f371959884352e7af68e6091c5bb330fcaa58b8/script/XYZ_Stake.s.sol#L41 - functionName: 'stake', + functionName: 'deposit', // TODO: Provide the first arg. (validator). Need to figure out how it works on Chainlink (vaults? single address?). See: https://github.com/webb-tools/tnt-core/blob/21c158d6cb11e2b5f50409d377431e7cd51ff72f/src/lst/adapters/ChainlinkAdapter.sol#L187 - args: ['0x0', BigInt(amount.toString())], + args: [activeEvmAddress20, BigInt(amount.toString())], }); if (depositTxReceipt.status === 'reverted') { @@ -97,7 +100,7 @@ const useLiquifierDeposit = () => { // Wait for the requirements to be ready before // returning the deposit function. - return isReady ? null : deposit; + return !isReady ? null : deposit; }; export default useLiquifierDeposit; diff --git a/apps/tangle-dapp/hooks/useTxNotification.tsx b/apps/tangle-dapp/hooks/useTxNotification.tsx index bee0b438b..f78fd88b4 100644 --- a/apps/tangle-dapp/hooks/useTxNotification.tsx +++ b/apps/tangle-dapp/hooks/useTxNotification.tsx @@ -44,6 +44,8 @@ const useTxNotification = (txName: TxName, explorerUrl?: string) => { const processingKey = `${txName}-processing`; + // TODO: Consider warning the user if they attempt to close the browser window. The transaction won't fail, but they won't be able to see the result. Might need a global store to track the event listener, to prevent multiple listeners from being added. + const notifySuccess = useCallback( (txHash: HexString, successMessage?: string | null) => { closeSnackbar(processingKey); From dfaaf21c347a935c114fb2e1f18faf0502308186 Mon Sep 17 00:00:00 2001 From: yurixander <101931215+yurixander@users.noreply.github.com> Date: Tue, 13 Aug 2024 15:11:09 -0400 Subject: [PATCH 07/60] refactor(tangle-dapp): Use `useCallback` --- .../stakeAndUnstake/ChainLogo.tsx | 18 ++-- .../stakeAndUnstake/LiquidStakeCard.tsx | 4 +- .../stakeAndUnstake/LiquidStakingInput.tsx | 12 +-- .../LiquidStakingTokenItem.tsx | 4 +- .../stakeAndUnstake/LiquidUnstakeCard.tsx | 8 +- .../stakeAndUnstake/SelectTokenModal.tsx | 4 +- apps/tangle-dapp/constants/liquidStaking.ts | 55 +++++------ apps/tangle-dapp/data/liquidStaking/store.ts | 6 +- .../liquidStaking/useLiquidStakingItems.ts | 46 +++++----- .../data/liquifier/useLiquifierDeposit.ts | 91 +++++++++++-------- 10 files changed, 134 insertions(+), 114 deletions(-) diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/ChainLogo.tsx b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/ChainLogo.tsx index 9e6232542..918de3642 100644 --- a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/ChainLogo.tsx +++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/ChainLogo.tsx @@ -2,12 +2,12 @@ import Image from 'next/image'; import { FC } from 'react'; import { twMerge } from 'tailwind-merge'; -import { LS_CHAIN_MAP, LsChainId } from '../../../constants/liquidStaking'; +import { LS_CHAIN_MAP, LsProtocolId } from '../../../constants/liquidStaking'; export type ChainLogoSize = 'sm' | 'md'; export type ChainLogoProps = { - chainId?: LsChainId; + chainId?: LsProtocolId; size: ChainLogoSize; isRounded?: boolean; isLiquidVariant?: boolean; @@ -31,21 +31,21 @@ const getSizeClass = (size: ChainLogoSize) => { } }; -const getBackgroundColor = (chain: LsChainId) => { +const getBackgroundColor = (chain: LsProtocolId) => { switch (chain) { - case LsChainId.MANTA: + case LsProtocolId.MANTA: return 'bg-[#13101D] dark:bg-[#13101D]'; - case LsChainId.MOONBEAM: + case LsProtocolId.MOONBEAM: return 'bg-[#1d1336] dark:bg-[#1d1336]'; - case LsChainId.PHALA: + case LsProtocolId.PHALA: return 'bg-black dark:bg-black'; - case LsChainId.POLKADOT: + case LsProtocolId.POLKADOT: return 'bg-mono-0 dark:bg-mono-0'; - case LsChainId.TANGLE_RESTAKING_PARACHAIN: + case LsProtocolId.TANGLE_RESTAKING_PARACHAIN: // Fix the icon SVG getting cut off on the sides by adding // a matching background. return 'bg-[#f6f4ff]'; - case LsChainId.ASTAR: + case LsProtocolId.ASTAR: // No background for Astar, since it looks better without // a background. return ''; diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakeCard.tsx b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakeCard.tsx index b660f9f9f..8acfff178 100644 --- a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakeCard.tsx +++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakeCard.tsx @@ -19,7 +19,7 @@ import { FC, useCallback, useMemo, useState } from 'react'; import { LST_PREFIX, LS_CHAIN_MAP, - LsChainId, + LsProtocolId, } from '../../../constants/liquidStaking'; import { useLiquidStakingStore } from '../../../data/liquidStaking/store'; import useExchangeRate, { @@ -132,7 +132,7 @@ const LiquidStakeCard: FC = () => { void; - setChainId?: (newChain: LsChainId) => void; + setChainId?: (newChain: LsProtocolId) => void; onTokenClick?: () => void; }; @@ -145,13 +145,13 @@ const LiquidStakingInput: FC = ({ }; type ChainSelectorProps = { - selectedChainId: LsChainId; + selectedChainId: LsProtocolId; /** * If this function is not provided, the selector will be * considered read-only. */ - setChainId?: (newChain: LsChainId) => void; + setChainId?: (newChain: LsProtocolId) => void; }; /** @internal */ @@ -183,7 +183,7 @@ const ChainSelector: FC = ({
      - {Object.values(LsChainId) + {Object.values(LsProtocolId) .filter((chainId) => chainId !== selectedChainId) .map((chainId) => { const chainName = LS_CHAIN_MAP[chainId].name; diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakingTokenItem.tsx b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakingTokenItem.tsx index 8bf44e9d3..8a021a830 100644 --- a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakingTokenItem.tsx +++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakingTokenItem.tsx @@ -10,7 +10,7 @@ import { StaticAssetPath } from '../../../constants'; import { LsToken, LST_PREFIX, - LsChainId, + LsProtocolId, TVS_TOOLTIP, } from '../../../constants/liquidStaking'; import { PagePath } from '../../../types'; @@ -19,7 +19,7 @@ import StatItem from '../StatItem'; import ChainLogo from './ChainLogo'; export type LiquidStakingTokenItemProps = { - chainId: LsChainId; + chainId: LsProtocolId; title: string; tokenSymbol: LsToken; totalValueStaked: number; diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidUnstakeCard.tsx b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidUnstakeCard.tsx index 9aac372de..fa653cb3f 100644 --- a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidUnstakeCard.tsx +++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidUnstakeCard.tsx @@ -13,7 +13,7 @@ import { FC, useCallback, useEffect, useMemo, useState } from 'react'; import { LST_PREFIX, LS_CHAIN_MAP, - LsChainId, + LsProtocolId, } from '../../../constants/liquidStaking'; import useDelegationsOccupiedStatus from '../../../data/liquidStaking/useDelegationsOccupiedStatus'; import useExchangeRate, { @@ -39,8 +39,8 @@ const LiquidUnstakeCard: FC = () => { const [isRequestSubmittedModalOpen, setIsRequestSubmittedModalOpen] = useState(false); - const [selectedChainId, setSelectedChainId] = useState( - LsChainId.TANGLE_RESTAKING_PARACHAIN, + const [selectedChainId, setSelectedChainId] = useState( + LsProtocolId.TANGLE_RESTAKING_PARACHAIN, ); const { @@ -150,7 +150,7 @@ const LiquidUnstakeCard: FC = () => { {/* TODO: Have a way to trigger a refresh of the amount once the wallet balance (max) button is clicked. Need to signal to the liquid staking input to update its display amount based on the `fromAmount` prop. */} = ({ > {/* Information */}
      - +
      diff --git a/apps/tangle-dapp/constants/liquidStaking.ts b/apps/tangle-dapp/constants/liquidStaking.ts index 3fe1b90e1..ce3ff01d7 100644 --- a/apps/tangle-dapp/constants/liquidStaking.ts +++ b/apps/tangle-dapp/constants/liquidStaking.ts @@ -7,7 +7,7 @@ import { TANGLE_RESTAKING_PARACHAIN_LOCAL_DEV_NETWORK } from '@webb-tools/webb-u import { StaticAssetPath } from '.'; -export enum LsChainId { +export enum LsProtocolId { POLKADOT = 'Polkadot', PHALA = 'Phala', MOONBEAM = 'Moonbeam', @@ -17,6 +17,8 @@ export enum LsChainId { CHAINLINK = 'Chainlink', } +export type Erc20ProtocolId = LsProtocolId.CHAINLINK; + export enum LsToken { DOT = 'DOT', GLMR = 'GLMR', @@ -32,8 +34,8 @@ export type ParachainCurrency = TanglePrimitivesCurrencyTokenSymbol['type']; // | Exclude // | 'Tnt'; -export type LsChainDef = { - id: LsChainId; +export type LsParachainProtocolDef = { + id: LsProtocolId; name: string; token: LsToken; logo: StaticAssetPath; @@ -43,8 +45,8 @@ export type LsChainDef = { rpcEndpoint: string; }; -const POLKADOT: LsChainDef = { - id: LsChainId.POLKADOT, +const POLKADOT: LsParachainProtocolDef = { + id: LsProtocolId.POLKADOT, name: 'Polkadot', token: LsToken.DOT, logo: StaticAssetPath.LIQUID_STAKING_TOKEN_POLKADOT, @@ -54,8 +56,8 @@ const POLKADOT: LsChainDef = { rpcEndpoint: 'wss://polkadot-rpc.dwellir.com', }; -const PHALA: LsChainDef = { - id: LsChainId.PHALA, +const PHALA: LsParachainProtocolDef = { + id: LsProtocolId.PHALA, name: 'Phala', token: LsToken.PHALA, logo: StaticAssetPath.LIQUID_STAKING_TOKEN_PHALA, @@ -65,8 +67,8 @@ const PHALA: LsChainDef = { rpcEndpoint: 'wss://api.phala.network/ws', }; -const MOONBEAM: LsChainDef = { - id: LsChainId.MOONBEAM, +const MOONBEAM: LsParachainProtocolDef = { + id: LsProtocolId.MOONBEAM, name: 'Moonbeam', token: LsToken.GLMR, logo: StaticAssetPath.LIQUID_STAKING_TOKEN_GLIMMER, @@ -77,8 +79,8 @@ const MOONBEAM: LsChainDef = { rpcEndpoint: 'wss://moonbeam.api.onfinality.io/public-ws', }; -const ASTAR: LsChainDef = { - id: LsChainId.ASTAR, +const ASTAR: LsParachainProtocolDef = { + id: LsProtocolId.ASTAR, name: 'Astar', token: LsToken.ASTAR, logo: StaticAssetPath.LIQUID_STAKING_TOKEN_ASTAR, @@ -89,8 +91,8 @@ const ASTAR: LsChainDef = { rpcEndpoint: 'wss://astar.api.onfinality.io/public-ws', }; -const MANTA: LsChainDef = { - id: LsChainId.MANTA, +const MANTA: LsParachainProtocolDef = { + id: LsProtocolId.MANTA, name: 'Manta', token: LsToken.MANTA, logo: StaticAssetPath.LIQUID_STAKING_TOKEN_MANTA, @@ -101,8 +103,8 @@ const MANTA: LsChainDef = { rpcEndpoint: 'wss://ws.manta.systems', }; -const TANGLE_RESTAKING_PARACHAIN: LsChainDef = { - id: LsChainId.TANGLE_RESTAKING_PARACHAIN, +const TANGLE_RESTAKING_PARACHAIN: LsParachainProtocolDef = { + id: LsProtocolId.TANGLE_RESTAKING_PARACHAIN, name: 'Tangle Parachain', token: LsToken.TNT, logo: StaticAssetPath.LIQUID_STAKING_TANGLE_LOGO, @@ -112,8 +114,8 @@ const TANGLE_RESTAKING_PARACHAIN: LsChainDef = { rpcEndpoint: TANGLE_RESTAKING_PARACHAIN_LOCAL_DEV_NETWORK.wsRpcEndpoint, }; -const CHAINLINK: LsChainDef = { - id: LsChainId.CHAINLINK, +const CHAINLINK: LsParachainProtocolDef = { + id: LsProtocolId.CHAINLINK, name: 'Chainlink', token: LsToken.LINK, networkName: 'Chainlink', @@ -124,17 +126,18 @@ const CHAINLINK: LsChainDef = { rpcEndpoint: 'wss://api.chain.link', }; -export const LS_CHAIN_MAP: Record = { - [LsChainId.POLKADOT]: POLKADOT, - [LsChainId.PHALA]: PHALA, - [LsChainId.MOONBEAM]: MOONBEAM, - [LsChainId.ASTAR]: ASTAR, - [LsChainId.MANTA]: MANTA, - [LsChainId.TANGLE_RESTAKING_PARACHAIN]: TANGLE_RESTAKING_PARACHAIN, - [LsChainId.CHAINLINK]: CHAINLINK, +export const LS_CHAIN_MAP: Record = { + [LsProtocolId.POLKADOT]: POLKADOT, + [LsProtocolId.PHALA]: PHALA, + [LsProtocolId.MOONBEAM]: MOONBEAM, + [LsProtocolId.ASTAR]: ASTAR, + [LsProtocolId.MANTA]: MANTA, + [LsProtocolId.TANGLE_RESTAKING_PARACHAIN]: TANGLE_RESTAKING_PARACHAIN, + [LsProtocolId.CHAINLINK]: CHAINLINK, }; -export const LIQUID_STAKING_CHAINS: LsChainDef[] = Object.values(LS_CHAIN_MAP); +export const LIQUID_STAKING_CHAINS: LsParachainProtocolDef[] = + Object.values(LS_CHAIN_MAP); export const TVS_TOOLTIP = "Total Value Staked (TVS) refers to the total value of assets that are currently staked for this network in fiat currency. Generally used as an indicator of a network's security and trustworthiness."; diff --git a/apps/tangle-dapp/data/liquidStaking/store.ts b/apps/tangle-dapp/data/liquidStaking/store.ts index 5619888f0..e9e2b9ebd 100644 --- a/apps/tangle-dapp/data/liquidStaking/store.ts +++ b/apps/tangle-dapp/data/liquidStaking/store.ts @@ -1,9 +1,9 @@ import { create } from 'zustand'; -import { LsChainId } from '../../constants/liquidStaking'; +import { LsProtocolId } from '../../constants/liquidStaking'; type State = { - selectedChainId: LsChainId; + selectedChainId: LsProtocolId; selectedItems: Set; }; @@ -15,7 +15,7 @@ type Actions = { type Store = State & Actions; export const useLiquidStakingStore = create((set) => ({ - selectedChainId: LsChainId.TANGLE_RESTAKING_PARACHAIN, + selectedChainId: LsProtocolId.TANGLE_RESTAKING_PARACHAIN, selectedItems: new Set(), setSelectedChainId: (selectedChainId) => set({ selectedChainId }), setSelectedItems: (selectedItems) => set({ selectedItems }), diff --git a/apps/tangle-dapp/data/liquidStaking/useLiquidStakingItems.ts b/apps/tangle-dapp/data/liquidStaking/useLiquidStakingItems.ts index fed8aa039..ce697bbd8 100644 --- a/apps/tangle-dapp/data/liquidStaking/useLiquidStakingItems.ts +++ b/apps/tangle-dapp/data/liquidStaking/useLiquidStakingItems.ts @@ -1,7 +1,7 @@ import { BN_ZERO } from '@polkadot/util'; import { useCallback, useEffect, useMemo, useState } from 'react'; -import { LS_CHAIN_MAP, LsChainId } from '../../constants/liquidStaking'; +import { LS_CHAIN_MAP, LsProtocolId } from '../../constants/liquidStaking'; import useLocalStorage, { LocalStorageKey } from '../../hooks/useLocalStorage'; import { Collator, @@ -24,7 +24,7 @@ import { fetchVaultsAndStakePools, } from './helper'; -const useLiquidStakingItems = (selectedChain: LsChainId) => { +const useLiquidStakingItems = (selectedChain: LsProtocolId) => { const { setWithPreviousValue: setLiquidStakingTableData } = useLocalStorage( LocalStorageKey.LIQUID_STAKING_TABLE_DATA, ); @@ -38,7 +38,7 @@ const useLiquidStakingItems = (selectedChain: LsChainId) => { const dataType = useMemo(() => getDataType(selectedChain), [selectedChain]); const fetchData = useCallback( - async (chain: LsChainId) => { + async (chain: LsProtocolId) => { const endpoint = LS_CHAIN_MAP[chain]?.rpcEndpoint; if (!endpoint) { @@ -51,30 +51,30 @@ const useLiquidStakingItems = (selectedChain: LsChainId) => { []; switch (chain) { - case LsChainId.POLKADOT: + case LsProtocolId.POLKADOT: fetchedItems = await getValidators(endpoint); break; - case LsChainId.ASTAR: + case LsProtocolId.ASTAR: fetchedItems = await getDapps(endpoint); break; - case LsChainId.PHALA: + case LsProtocolId.PHALA: fetchedItems = await getVaultsAndStakePools(endpoint); break; - case LsChainId.MOONBEAM: + case LsProtocolId.MOONBEAM: fetchedItems = await getCollators( endpoint, - LsChainId.MOONBEAM, + LsProtocolId.MOONBEAM, 'https://stakeglmr.com/', ); break; - case LsChainId.MANTA: + case LsProtocolId.MANTA: fetchedItems = await getCollators( endpoint, - LsChainId.MANTA, + LsProtocolId.MANTA, 'https://manta.subscan.io/account/', ); break; @@ -110,18 +110,18 @@ const useLiquidStakingItems = (selectedChain: LsChainId) => { export default useLiquidStakingItems; -const getDataType = (chain: LsChainId) => { +const getDataType = (chain: LsProtocolId) => { switch (chain) { - case LsChainId.MANTA: + case LsProtocolId.MANTA: return LiquidStakingItem.COLLATOR; - case LsChainId.MOONBEAM: + case LsProtocolId.MOONBEAM: return LiquidStakingItem.COLLATOR; - case LsChainId.TANGLE_RESTAKING_PARACHAIN: - case LsChainId.POLKADOT: + case LsProtocolId.TANGLE_RESTAKING_PARACHAIN: + case LsProtocolId.POLKADOT: return LiquidStakingItem.VALIDATOR; - case LsChainId.PHALA: + case LsProtocolId.PHALA: return LiquidStakingItem.VAULT_OR_STAKE_POOL; - case LsChainId.ASTAR: + case LsProtocolId.ASTAR: return LiquidStakingItem.DAPP; default: return LiquidStakingItem.VALIDATOR; @@ -157,7 +157,7 @@ const getValidators = async (endpoint: string): Promise => { totalValueStaked: totalValueStaked || BN_ZERO, validatorAPY: 0, validatorCommission: commission || BN_ZERO, - chain: LsChainId.POLKADOT, + chain: LsProtocolId.POLKADOT, chainDecimals, chainTokenSymbol, itemType: LiquidStakingItem.VALIDATOR, @@ -204,7 +204,7 @@ const getDapps = async (endpoint: string): Promise => { dappContractType: dappInfo ? dappInfo.contractType || '' : '', commission: BN_ZERO, totalValueStaked: totalValueStaked || BN_ZERO, - chain: LsChainId.ASTAR, + chain: LsProtocolId.ASTAR, chainDecimals, chainTokenSymbol, itemType: LiquidStakingItem.DAPP, @@ -234,7 +234,7 @@ const getVaultsAndStakePools = async ( vaultOrStakePoolName: val.id, commission: val.commission, totalValueStaked: val.totalValueStaked, - chain: LsChainId.PHALA, + chain: LsProtocolId.PHALA, chainDecimals, chainTokenSymbol, type, @@ -246,7 +246,7 @@ const getVaultsAndStakePools = async ( const getCollators = async ( endpoint: string, - chain: LsChainId, + chain: LsProtocolId, href: string, ): Promise => { const [ @@ -269,9 +269,9 @@ const getCollators = async ( let collatorExternalLink = ''; - if (chain === LsChainId.MOONBEAM) { + if (chain === LsProtocolId.MOONBEAM) { collatorExternalLink = href; - } else if (chain === LsChainId.MANTA) { + } else if (chain === LsProtocolId.MANTA) { collatorExternalLink = href + collator; } diff --git a/apps/tangle-dapp/data/liquifier/useLiquifierDeposit.ts b/apps/tangle-dapp/data/liquifier/useLiquifierDeposit.ts index 66199ee80..43a372459 100644 --- a/apps/tangle-dapp/data/liquifier/useLiquifierDeposit.ts +++ b/apps/tangle-dapp/data/liquifier/useLiquifierDeposit.ts @@ -1,5 +1,6 @@ import { BN } from '@polkadot/util'; import assert from 'assert'; +import { useCallback } from 'react'; import { erc20Abi } from 'viem'; import { TxName } from '../../constants'; @@ -48,55 +49,71 @@ const useLiquifierDeposit = () => { writeChainlinkErc20 !== null && activeEvmAddress20 !== null; - const deposit = async (token: Erc20TokenId, amount: BN) => { - // TODO: Need to call `Token.approve` before calling `Liquifier.stake`. This is not implemented yet. Also, should the user balance check be done here or assume that the consumer of the hook will handle that? + const deposit = useCallback( + async (token: Erc20TokenId, amount: BN) => { + // TODO: Should the user balance check be done here or assume that the consumer of the hook will handle that? - assert( - isReady, - 'Should not be able to call this function if the requirements are not ready yet', - ); + assert( + isReady, + 'Should not be able to call this function if the requirements are not ready yet', + ); - const tokenDef = ERC20_TOKEN_MAP[token]; + const tokenDef = ERC20_TOKEN_MAP[token]; - notifyApproveProcessing(); + notifyApproveProcessing(); - // Approve spending the token amount by the Liquifier contract. - const approveTxReceipt = await writeChainlinkErc20({ - address: tokenDef.address, - functionName: 'approve', - args: [tokenDef.liquifierAdapterAddress, BigInt(amount.toString())], - }); + // Approve spending the token amount by the Liquifier contract. + const approveTxReceipt = await writeChainlinkErc20({ + address: tokenDef.address, + functionName: 'approve', + args: [tokenDef.liquifierAdapterAddress, BigInt(amount.toString())], + }); - if (approveTxReceipt.status === 'reverted') { - notifyApproveError( - 'Failed to approve spending the token amount by the Liquifier contract', - ); + if (approveTxReceipt.status === 'reverted') { + notifyApproveError( + 'Failed to approve spending the token amount by the Liquifier contract', + ); - return false; - } + return false; + } - // TODO: Ensure that the transaction hash takes to the proper explorer URL, since it is not a Substrate transaction. - notifyApproveSuccess(approveTxReceipt.transactionHash); - notifyDepositProcessing(); + // TODO: Ensure that the transaction hash takes to the proper explorer URL, since it is not a Substrate transaction. + notifyApproveSuccess(approveTxReceipt.transactionHash); + notifyDepositProcessing(); - const depositTxReceipt = await writeLiquifier({ - // TODO: Does the adapter contract have a deposit function? It doesn't seem like so. In that case, will need to update the way that Liquifier contract's address is handled. - address: tokenDef.liquifierAdapterAddress, - functionName: 'deposit', - // TODO: Provide the first arg. (validator). Need to figure out how it works on Chainlink (vaults? single address?). See: https://github.com/webb-tools/tnt-core/blob/21c158d6cb11e2b5f50409d377431e7cd51ff72f/src/lst/adapters/ChainlinkAdapter.sol#L187 - args: [activeEvmAddress20, BigInt(amount.toString())], - }); + const depositTxReceipt = await writeLiquifier({ + // TODO: Does the adapter contract have a deposit function? It doesn't seem like so. In that case, will need to update the way that Liquifier contract's address is handled. + address: tokenDef.liquifierAdapterAddress, + functionName: 'deposit', + // TODO: Provide the first arg. (validator). Need to figure out how it works on Chainlink (vaults? single address?). See: https://github.com/webb-tools/tnt-core/blob/21c158d6cb11e2b5f50409d377431e7cd51ff72f/src/lst/adapters/ChainlinkAdapter.sol#L187 + args: [activeEvmAddress20, BigInt(amount.toString())], + }); - if (depositTxReceipt.status === 'reverted') { - notifyDepositError('Failed to deposit the token amount to the Liquifier'); + if (depositTxReceipt.status === 'reverted') { + notifyDepositError( + 'Failed to deposit the token amount to the Liquifier', + ); - return false; - } + return false; + } - notifyDepositSuccess(depositTxReceipt.transactionHash); + notifyDepositSuccess(depositTxReceipt.transactionHash); - return true; - }; + return true; + }, + [ + activeEvmAddress20, + isReady, + notifyApproveError, + notifyApproveProcessing, + notifyApproveSuccess, + notifyDepositError, + notifyDepositProcessing, + notifyDepositSuccess, + writeChainlinkErc20, + writeLiquifier, + ], + ); // Wait for the requirements to be ready before // returning the deposit function. From 9326d5e963dd6791536aae7df37cff5fd7cb8115 Mon Sep 17 00:00:00 2001 From: yurixander <101931215+yurixander@users.noreply.github.com> Date: Tue, 13 Aug 2024 15:33:23 -0400 Subject: [PATCH 08/60] fix(tangle-dapp): Fix merge resolution leftovers --- .../stakeAndUnstake/LiquidStakingInput.tsx | 7 ++--- apps/tangle-dapp/hooks/useLSTokenSVGs.ts | 30 ------------------- apps/tangle-dapp/types/liquidStaking.ts | 4 +-- 3 files changed, 4 insertions(+), 37 deletions(-) delete mode 100644 apps/tangle-dapp/hooks/useLSTokenSVGs.ts diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakingInput.tsx b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakingInput.tsx index 05a937906..2265df16f 100644 --- a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakingInput.tsx +++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakingInput.tsx @@ -160,7 +160,6 @@ const ChainSelector: FC = ({ setChainId, }) => { const isReadOnly = setChainId === undefined; - const selectedChainName = LS_CHAIN_MAP[selectedChainId].name; const base = (
      @@ -168,7 +167,7 @@ const ChainSelector: FC = ({ - {PARACHAIN_CHAIN_MAP[selectedChainId].networkName} + {LS_CHAIN_MAP[selectedChainId].networkName}
      @@ -189,8 +188,6 @@ const ChainSelector: FC = ({ chainId !== selectedChainId && typeof chainId !== 'string', ) .map((chainId) => { - const chainName = LS_CHAIN_MAP[chainId].name; - return (
    • = ({ onSelect={() => setChainId(chainId)} className="px-3 normal-case" > - {PARACHAIN_CHAIN_MAP[chainId].networkName} + {LS_CHAIN_MAP[chainId].networkName}
    • ); diff --git a/apps/tangle-dapp/hooks/useLSTokenSVGs.ts b/apps/tangle-dapp/hooks/useLSTokenSVGs.ts deleted file mode 100644 index 82192375e..000000000 --- a/apps/tangle-dapp/hooks/useLSTokenSVGs.ts +++ /dev/null @@ -1,30 +0,0 @@ -import ASTR from '@webb-tools/icons/LiquidStakingTokens/ASTAR.svg'; -import DOT from '@webb-tools/icons/LiquidStakingTokens/DOT.svg'; -import GLMR from '@webb-tools/icons/LiquidStakingTokens/GLMR.svg'; -import MANTA from '@webb-tools/icons/LiquidStakingTokens/MANTA.svg'; -import PHALA from '@webb-tools/icons/LiquidStakingTokens/PHALA.svg'; -import TNT from '@webb-tools/icons/LiquidStakingTokens/TNT.svg'; -import React, { useMemo } from 'react'; - -import { LsToken } from '../constants/liquidStaking'; - -const tokenSVGs: { - [key in LsToken]: React.FC>; -} = { - DOT, - GLMR, - MANTA, - ASTR, - PHALA, - TNT, -}; - -const useLSTokenSVGs = ( - tokenSymbol: LsToken, -): React.FC> | null => { - const TokenSVG = useMemo(() => tokenSVGs[tokenSymbol] || null, [tokenSymbol]); - - return TokenSVG; -}; - -export default useLSTokenSVGs; diff --git a/apps/tangle-dapp/types/liquidStaking.ts b/apps/tangle-dapp/types/liquidStaking.ts index da3698140..20ac9f88a 100644 --- a/apps/tangle-dapp/types/liquidStaking.ts +++ b/apps/tangle-dapp/types/liquidStaking.ts @@ -1,13 +1,13 @@ import { BN } from '@polkadot/util'; -import { ParachainChainId } from '../constants/liquidStaking'; +import { LsProtocolId } from '../constants/liquidStaking'; // All chains export type StakingItem = { id: string; // address - Validator, contract address - DAPP, pool/vault ID - VaultOrStakePool totalValueStaked: BN; minimumStake?: BN; - chainId: ParachainChainId; + chainId: LsProtocolId; chainDecimals: number; chainTokenSymbol: string; itemType: LiquidStakingItem; From 7a563cd90493b258aabaab07ecba3ac2f80c077e Mon Sep 17 00:00:00 2001 From: yurixander <101931215+yurixander@users.noreply.github.com> Date: Tue, 13 Aug 2024 16:08:10 -0400 Subject: [PATCH 09/60] refactor(tangle-dapp): Organize files --- .../app/liquid-staking/[tokenSymbol]/page.tsx | 6 +- apps/tangle-dapp/app/liquid-staking/page.tsx | 2 +- .../components/Breadcrumbs/utils.tsx | 2 +- .../LiquidStakingSelectionTable.tsx | 2 +- .../stakeAndUnstake/ChainLogo.tsx | 21 +++--- .../ExchangeRateDetailItem.tsx | 6 +- .../stakeAndUnstake/LiquidStakeCard.tsx | 8 +-- .../stakeAndUnstake/LiquidStakingInput.tsx | 20 +++--- .../LiquidStakingTokenItem.tsx | 10 +-- .../stakeAndUnstake/LiquidUnstakeCard.tsx | 13 ++-- .../MintAndRedeemFeeDetailItem.tsx | 4 +- .../ParachainWalletBalance.tsx | 4 +- .../stakeAndUnstake/SelectTokenModal.tsx | 4 +- .../stakeAndUnstake/TokenChip.tsx | 6 +- .../UnstakePeriodDetailItem.tsx | 2 +- .../RebondLstUnstakeRequestButton.tsx | 2 +- .../UnstakeRequestsTable.tsx | 2 +- .../WithdrawLstUnstakeRequestButton.tsx | 2 +- .../useLstUnlockRequestTableRows.ts | 2 +- .../liquidStaking/liquidStakingEvm.ts} | 14 ++-- .../liquidStakingParachain.ts} | 72 ++++++++----------- .../liquidStaking}/liquifierAbi.ts | 4 +- .../liquifierChainlinkAdapterAbi.ts | 4 +- .../liquidStaking/getValueOfTangleCurrency.ts | 2 +- .../useDelegationsOccupiedStatus.ts | 2 +- .../data/liquidStaking/useExchangeRate.ts | 2 +- .../liquidStaking/useLiquidStakingItems.ts | 49 +++++++------ .../{store.ts => useLiquidStakingStore.ts} | 6 +- .../data/liquidStaking/useLstRebondTx.ts | 2 +- .../liquidStaking/useLstUnlockRequests.ts | 2 +- .../liquidStaking/useLstWithdrawRedeemTx.ts | 2 +- .../data/liquidStaking/useMintTx.ts | 2 +- .../data/liquidStaking/useOngoingTimeUnits.ts | 2 +- .../liquidStaking/useParachainBalances.ts | 10 +-- .../data/liquidStaking/useRedeemTx.ts | 2 +- .../liquidStaking/useTokenUnlockDurations.ts | 2 +- .../liquidStaking/useTokenUnlockLedger.ts | 2 +- .../data/liquifier/useLiquifierDeposit.ts | 11 +-- .../hooks/useEvmPrecompileAbiCall.ts | 1 - apps/tangle-dapp/types/liquidStaking.ts | 4 +- .../liquidStaking/isLiquidStakingToken.ts | 7 -- .../utils/liquidStaking/isLsParachainToken.ts | 11 +++ .../utils/liquidStaking/stringifyTimeUnit.ts | 2 +- .../tangleTimeUnitToSimpleInstance.ts | 2 +- 44 files changed, 169 insertions(+), 168 deletions(-) rename apps/tangle-dapp/{data/liquifier/erc20Tokens.ts => constants/liquidStaking/liquidStakingEvm.ts} (60%) rename apps/tangle-dapp/constants/{liquidStaking.ts => liquidStaking/liquidStakingParachain.ts} (73%) rename apps/tangle-dapp/{data/liquifier => constants/liquidStaking}/liquifierAbi.ts (98%) rename apps/tangle-dapp/{data/liquifier => constants/liquidStaking}/liquifierChainlinkAdapterAbi.ts (99%) rename apps/tangle-dapp/data/liquidStaking/{store.ts => useLiquidStakingStore.ts} (72%) delete mode 100644 apps/tangle-dapp/utils/liquidStaking/isLiquidStakingToken.ts create mode 100644 apps/tangle-dapp/utils/liquidStaking/isLsParachainToken.ts diff --git a/apps/tangle-dapp/app/liquid-staking/[tokenSymbol]/page.tsx b/apps/tangle-dapp/app/liquid-staking/[tokenSymbol]/page.tsx index 7b24f2d5a..e2057c51a 100644 --- a/apps/tangle-dapp/app/liquid-staking/[tokenSymbol]/page.tsx +++ b/apps/tangle-dapp/app/liquid-staking/[tokenSymbol]/page.tsx @@ -7,9 +7,9 @@ import { LiquidStakingSelectionTable } from '../../../components/LiquidStaking/L import LiquidStakeCard from '../../../components/LiquidStaking/stakeAndUnstake/LiquidStakeCard'; import LiquidUnstakeCard from '../../../components/LiquidStaking/stakeAndUnstake/LiquidUnstakeCard'; import UnstakeRequestsTable from '../../../components/LiquidStaking/unstakeRequestsTable/UnstakeRequestsTable'; -import { LsSearchParamKey } from '../../../constants/liquidStaking'; +import { LsSearchParamKey } from '../../../constants/liquidStaking/liquidStakingParachain'; import useSearchParamState from '../../../hooks/useSearchParamState'; -import isLiquidStakingToken from '../../../utils/liquidStaking/isLiquidStakingToken'; +import isLsParachainToken from '../../../utils/liquidStaking/isLsParachainToken'; import TabListItem from '../../restake/TabListItem'; import TabsList from '../../restake/TabsList'; @@ -26,7 +26,7 @@ const LiquidStakingTokenPage: FC = ({ params: { tokenSymbol } }) => { stringify: (value) => (value ? 'stake' : 'unstake'), }); - if (!isLiquidStakingToken(tokenSymbol)) { + if (!isLsParachainToken(tokenSymbol)) { return notFound(); } diff --git a/apps/tangle-dapp/app/liquid-staking/page.tsx b/apps/tangle-dapp/app/liquid-staking/page.tsx index 446da5459..055746510 100644 --- a/apps/tangle-dapp/app/liquid-staking/page.tsx +++ b/apps/tangle-dapp/app/liquid-staking/page.tsx @@ -4,7 +4,7 @@ import { FC } from 'react'; import { GlassCard } from '../../components'; import LiquidStakingTokenItem from '../../components/LiquidStaking/stakeAndUnstake/LiquidStakingTokenItem'; import StatItem from '../../components/LiquidStaking/StatItem'; -import { LIQUID_STAKING_CHAINS } from '../../constants/liquidStaking'; +import { LIQUID_STAKING_CHAINS } from '../../constants/liquidStaking/liquidStakingParachain'; const LiquidStakingPage: FC = () => { return ( diff --git a/apps/tangle-dapp/components/Breadcrumbs/utils.tsx b/apps/tangle-dapp/components/Breadcrumbs/utils.tsx index 5b30136d1..e66f3a877 100644 --- a/apps/tangle-dapp/components/Breadcrumbs/utils.tsx +++ b/apps/tangle-dapp/components/Breadcrumbs/utils.tsx @@ -15,7 +15,7 @@ import assert from 'assert'; import capitalize from 'lodash/capitalize'; import { JSX } from 'react'; -import { LST_PREFIX } from '../../constants/liquidStaking'; +import { LST_PREFIX } from '../../constants/liquidStaking/liquidStakingParachain'; import { PagePath } from '../../types'; const BREADCRUMB_ICONS: Record JSX.Element> = { diff --git a/apps/tangle-dapp/components/LiquidStaking/LiquidStakingSelectionTable.tsx b/apps/tangle-dapp/components/LiquidStaking/LiquidStakingSelectionTable.tsx index 2176a2953..dfd8476e7 100644 --- a/apps/tangle-dapp/components/LiquidStaking/LiquidStakingSelectionTable.tsx +++ b/apps/tangle-dapp/components/LiquidStaking/LiquidStakingSelectionTable.tsx @@ -24,7 +24,7 @@ import { } from '@webb-tools/webb-ui-components'; import { useEffect, useMemo, useRef, useState } from 'react'; -import { useLiquidStakingStore } from '../../data/liquidStaking/store'; +import { useLiquidStakingStore } from '../../data/liquidStaking/useLiquidStakingStore'; import useLiquidStakingItems from '../../data/liquidStaking/useLiquidStakingItems'; import { useLiquidStakingSelectionTableColumns } from '../../hooks/LiquidStaking/useLiquidStakingSelectionTableColumns'; import { diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/ChainLogo.tsx b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/ChainLogo.tsx index 918de3642..e08e490ce 100644 --- a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/ChainLogo.tsx +++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/ChainLogo.tsx @@ -2,12 +2,15 @@ import Image from 'next/image'; import { FC } from 'react'; import { twMerge } from 'tailwind-merge'; -import { LS_CHAIN_MAP, LsProtocolId } from '../../../constants/liquidStaking'; +import { + LS_CHAIN_MAP, + LsParachainChainId, +} from '../../../constants/liquidStaking/liquidStakingParachain'; export type ChainLogoSize = 'sm' | 'md'; export type ChainLogoProps = { - chainId?: LsProtocolId; + chainId?: LsParachainChainId; size: ChainLogoSize; isRounded?: boolean; isLiquidVariant?: boolean; @@ -31,21 +34,21 @@ const getSizeClass = (size: ChainLogoSize) => { } }; -const getBackgroundColor = (chain: LsProtocolId) => { +const getBackgroundColor = (chain: LsParachainChainId) => { switch (chain) { - case LsProtocolId.MANTA: + case LsParachainChainId.MANTA: return 'bg-[#13101D] dark:bg-[#13101D]'; - case LsProtocolId.MOONBEAM: + case LsParachainChainId.MOONBEAM: return 'bg-[#1d1336] dark:bg-[#1d1336]'; - case LsProtocolId.PHALA: + case LsParachainChainId.PHALA: return 'bg-black dark:bg-black'; - case LsProtocolId.POLKADOT: + case LsParachainChainId.POLKADOT: return 'bg-mono-0 dark:bg-mono-0'; - case LsProtocolId.TANGLE_RESTAKING_PARACHAIN: + case LsParachainChainId.TANGLE_RESTAKING_PARACHAIN: // Fix the icon SVG getting cut off on the sides by adding // a matching background. return 'bg-[#f6f4ff]'; - case LsProtocolId.ASTAR: + case LsParachainChainId.ASTAR: // No background for Astar, since it looks better without // a background. return ''; diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/ExchangeRateDetailItem.tsx b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/ExchangeRateDetailItem.tsx index 605841150..53f3fd529 100644 --- a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/ExchangeRateDetailItem.tsx +++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/ExchangeRateDetailItem.tsx @@ -2,10 +2,10 @@ import { SkeletonLoader } from '@webb-tools/webb-ui-components'; import { FC } from 'react'; import { - LsToken, + LsParachainToken, LST_PREFIX, ParachainCurrency, -} from '../../../constants/liquidStaking'; +} from '../../../constants/liquidStaking/liquidStakingParachain'; import useExchangeRate, { ExchangeRateType, } from '../../../data/liquidStaking/useExchangeRate'; @@ -13,7 +13,7 @@ import DetailItem from './DetailItem'; export type ExchangeRateDetailItemProps = { type: ExchangeRateType; - token: LsToken; + token: LsParachainToken; currency: ParachainCurrency; }; diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakeCard.tsx b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakeCard.tsx index dfe7cda9a..55924a960 100644 --- a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakeCard.tsx +++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakeCard.tsx @@ -21,9 +21,9 @@ import { LsSearchParamKey, LST_PREFIX, LS_CHAIN_MAP, - LsProtocolId, -} from '../../../constants/liquidStaking'; -import { useLiquidStakingStore } from '../../../data/liquidStaking/store'; + LsParachainChainId, +} from '../../../constants/liquidStaking/liquidStakingParachain'; +import { useLiquidStakingStore } from '../../../data/liquidStaking/useLiquidStakingStore'; import useExchangeRate, { ExchangeRateType, } from '../../../data/liquidStaking/useExchangeRate'; @@ -148,7 +148,7 @@ const LiquidStakeCard: FC = () => { void; - setChainId?: (newChain: LsProtocolId) => void; + setChainId?: (newChain: LsParachainChainId) => void; onTokenClick?: () => void; }; @@ -145,13 +145,13 @@ const LiquidStakingInput: FC = ({ }; type ChainSelectorProps = { - selectedChainId: LsProtocolId; + selectedChainId: LsParachainChainId; /** * If this function is not provided, the selector will be * considered read-only. */ - setChainId?: (newChain: LsProtocolId) => void; + setChainId?: (newChain: LsParachainChainId) => void; }; /** @internal */ @@ -182,9 +182,9 @@ const ChainSelector: FC = ({
        - {Object.values(LsProtocolId) + {Object.values(LsParachainChainId) .filter( - (chainId): chainId is LsProtocolId => + (chainId): chainId is LsParachainChainId => chainId !== selectedChainId && typeof chainId !== 'string', ) .map((chainId) => { diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakingTokenItem.tsx b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakingTokenItem.tsx index 8a021a830..cd074256a 100644 --- a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakingTokenItem.tsx +++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakingTokenItem.tsx @@ -8,20 +8,20 @@ import { FC, useMemo } from 'react'; import { StaticAssetPath } from '../../../constants'; import { - LsToken, + LsParachainToken, LST_PREFIX, - LsProtocolId, + LsParachainChainId, TVS_TOOLTIP, -} from '../../../constants/liquidStaking'; +} from '../../../constants/liquidStaking/liquidStakingParachain'; import { PagePath } from '../../../types'; import formatTangleBalance from '../../../utils/formatTangleBalance'; import StatItem from '../StatItem'; import ChainLogo from './ChainLogo'; export type LiquidStakingTokenItemProps = { - chainId: LsProtocolId; + chainId: LsParachainChainId; title: string; - tokenSymbol: LsToken; + tokenSymbol: LsParachainToken; totalValueStaked: number; totalStaked: string; }; diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidUnstakeCard.tsx b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidUnstakeCard.tsx index 9b83899dc..d3d0e26eb 100644 --- a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidUnstakeCard.tsx +++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidUnstakeCard.tsx @@ -13,10 +13,10 @@ import { z } from 'zod'; import { LS_CHAIN_MAP, - LsProtocolId, + LsParachainChainId, LsSearchParamKey, LST_PREFIX, -} from '../../../constants/liquidStaking'; +} from '../../../constants/liquidStaking/liquidStakingParachain'; import useDelegationsOccupiedStatus from '../../../data/liquidStaking/useDelegationsOccupiedStatus'; import useExchangeRate, { ExchangeRateType, @@ -44,10 +44,11 @@ const LiquidUnstakeCard: FC = () => { useState(false); const [selectedChainId, setSelectedChainId] = - useSearchParamState({ + useSearchParamState({ key: LsSearchParamKey.CHAIN_ID, - defaultValue: LsProtocolId.TANGLE_RESTAKING_PARACHAIN, - parser: (value) => z.nativeEnum(LsProtocolId).parse(parseInt(value)), + defaultValue: LsParachainChainId.TANGLE_RESTAKING_PARACHAIN, + parser: (value) => + z.nativeEnum(LsParachainChainId).parse(parseInt(value)), stringify: (value) => value.toString(), }); @@ -166,7 +167,7 @@ const LiquidUnstakeCard: FC = () => { {/* TODO: Have a way to trigger a refresh of the amount once the wallet balance (max) button is clicked. Need to signal to the liquid staking input to update its display amount based on the `fromAmount` prop. */} = ({ diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/ParachainWalletBalance.tsx b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/ParachainWalletBalance.tsx index 27b675cdf..2a9aab2d3 100644 --- a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/ParachainWalletBalance.tsx +++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/ParachainWalletBalance.tsx @@ -13,14 +13,14 @@ import { FC, useCallback, useMemo, useState } from 'react'; import { twMerge } from 'tailwind-merge'; import { EMPTY_VALUE_PLACEHOLDER } from '../../../constants'; -import { LsToken } from '../../../constants/liquidStaking'; +import { LsParachainToken } from '../../../constants/liquidStaking/liquidStakingParachain'; import useParachainBalances from '../../../data/liquidStaking/useParachainBalances'; import useSubstrateAddress from '../../../hooks/useSubstrateAddress'; import formatBn from '../../../utils/formatBn'; export type ParachainWalletBalanceProps = { isNative?: boolean; - token: LsToken; + token: LsParachainToken; decimals: number; tooltip?: string; onlyShowTooltipWhenBalanceIsSet?: boolean; diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/SelectTokenModal.tsx b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/SelectTokenModal.tsx index 6232e2ef2..a04366c20 100644 --- a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/SelectTokenModal.tsx +++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/SelectTokenModal.tsx @@ -8,7 +8,7 @@ import { } from '@webb-tools/webb-ui-components'; import { FC, useEffect, useMemo } from 'react'; -import { LsProtocolId } from '../../../constants/liquidStaking'; +import { LsParachainChainId } from '../../../constants/liquidStaking/liquidStakingParachain'; import { AnySubstrateAddress } from '../../../types/utils'; import formatBn from '../../../utils/formatBn'; import AddressLink from '../AddressLink'; @@ -106,7 +106,7 @@ const TokenListItem: FC = ({ > {/* Information */}
        - +
        diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/TokenChip.tsx b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/TokenChip.tsx index b09bda068..a665b7ffc 100644 --- a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/TokenChip.tsx +++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/TokenChip.tsx @@ -5,14 +5,14 @@ import { twMerge } from 'tailwind-merge'; import { LIQUID_STAKING_CHAINS, - LsToken, + LsParachainToken, LST_PREFIX, -} from '../../../constants/liquidStaking'; +} from '../../../constants/liquidStaking/liquidStakingParachain'; import ChainLogo from './ChainLogo'; import DropdownChevronIcon from './DropdownChevronIcon'; type TokenChipProps = { - token?: LsToken; + token?: LsParachainToken; isLiquidVariant: boolean; onClick?: () => void; }; diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/UnstakePeriodDetailItem.tsx b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/UnstakePeriodDetailItem.tsx index 7703e6702..a1d65aa47 100644 --- a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/UnstakePeriodDetailItem.tsx +++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/UnstakePeriodDetailItem.tsx @@ -1,7 +1,7 @@ import { SkeletonLoader } from '@webb-tools/webb-ui-components'; import { FC, useMemo } from 'react'; -import { ParachainCurrency } from '../../../constants/liquidStaking'; +import { ParachainCurrency } from '../../../constants/liquidStaking/liquidStakingParachain'; import useTokenUnlockDurations from '../../../data/liquidStaking/useTokenUnlockDurations'; import stringifyTimeUnit from '../../../utils/liquidStaking/stringifyTimeUnit'; import DetailItem from './DetailItem'; diff --git a/apps/tangle-dapp/components/LiquidStaking/unstakeRequestsTable/RebondLstUnstakeRequestButton.tsx b/apps/tangle-dapp/components/LiquidStaking/unstakeRequestsTable/RebondLstUnstakeRequestButton.tsx index cd26ffc58..1e1212b26 100644 --- a/apps/tangle-dapp/components/LiquidStaking/unstakeRequestsTable/RebondLstUnstakeRequestButton.tsx +++ b/apps/tangle-dapp/components/LiquidStaking/unstakeRequestsTable/RebondLstUnstakeRequestButton.tsx @@ -2,7 +2,7 @@ import { Button } from '@webb-tools/webb-ui-components'; import assert from 'assert'; import { FC, useCallback, useEffect, useState } from 'react'; -import { ParachainCurrency } from '../../../constants/liquidStaking'; +import { ParachainCurrency } from '../../../constants/liquidStaking/liquidStakingParachain'; import useLstRebondTx from '../../../data/liquidStaking/useLstRebondTx'; import { TxStatus } from '../../../hooks/useSubstrateTx'; import CancelUnstakeModal from './CancelUnstakeModal'; diff --git a/apps/tangle-dapp/components/LiquidStaking/unstakeRequestsTable/UnstakeRequestsTable.tsx b/apps/tangle-dapp/components/LiquidStaking/unstakeRequestsTable/UnstakeRequestsTable.tsx index d751214ec..17886ee75 100644 --- a/apps/tangle-dapp/components/LiquidStaking/unstakeRequestsTable/UnstakeRequestsTable.tsx +++ b/apps/tangle-dapp/components/LiquidStaking/unstakeRequestsTable/UnstakeRequestsTable.tsx @@ -30,7 +30,7 @@ import { twMerge } from 'tailwind-merge'; import { ParachainCurrency, LsSimpleParachainTimeUnit, -} from '../../../constants/liquidStaking'; +} from '../../../constants/liquidStaking/liquidStakingParachain'; import useSubstrateAddress from '../../../hooks/useSubstrateAddress'; import { AnySubstrateAddress } from '../../../types/utils'; import stringifyTimeUnit from '../../../utils/liquidStaking/stringifyTimeUnit'; diff --git a/apps/tangle-dapp/components/LiquidStaking/unstakeRequestsTable/WithdrawLstUnstakeRequestButton.tsx b/apps/tangle-dapp/components/LiquidStaking/unstakeRequestsTable/WithdrawLstUnstakeRequestButton.tsx index e66cc01a8..dadd0a723 100644 --- a/apps/tangle-dapp/components/LiquidStaking/unstakeRequestsTable/WithdrawLstUnstakeRequestButton.tsx +++ b/apps/tangle-dapp/components/LiquidStaking/unstakeRequestsTable/WithdrawLstUnstakeRequestButton.tsx @@ -2,7 +2,7 @@ import { Button } from '@webb-tools/webb-ui-components'; import assert from 'assert'; import { FC, useCallback, useEffect, useState } from 'react'; -import { ParachainCurrency } from '../../../constants/liquidStaking'; +import { ParachainCurrency } from '../../../constants/liquidStaking/liquidStakingParachain'; import useLstWithdrawRedeemTx from '../../../data/liquidStaking/useLstWithdrawRedeemTx'; import { TxStatus } from '../../../hooks/useSubstrateTx'; import WithdrawalConfirmationModal from './WithdrawalConfirmationModal'; diff --git a/apps/tangle-dapp/components/LiquidStaking/unstakeRequestsTable/useLstUnlockRequestTableRows.ts b/apps/tangle-dapp/components/LiquidStaking/unstakeRequestsTable/useLstUnlockRequestTableRows.ts index 9d9ce5f0c..5348de38f 100644 --- a/apps/tangle-dapp/components/LiquidStaking/unstakeRequestsTable/useLstUnlockRequestTableRows.ts +++ b/apps/tangle-dapp/components/LiquidStaking/unstakeRequestsTable/useLstUnlockRequestTableRows.ts @@ -4,7 +4,7 @@ import { useMemo } from 'react'; import { LIQUID_STAKING_CHAINS, LsSimpleParachainTimeUnit, -} from '../../../constants/liquidStaking'; +} from '../../../constants/liquidStaking/liquidStakingParachain'; import useLstUnlockRequests from '../../../data/liquidStaking/useLstUnlockRequests'; import useOngoingTimeUnits from '../../../data/liquidStaking/useOngoingTimeUnits'; import { AnySubstrateAddress } from '../../../types/utils'; diff --git a/apps/tangle-dapp/data/liquifier/erc20Tokens.ts b/apps/tangle-dapp/constants/liquidStaking/liquidStakingEvm.ts similarity index 60% rename from apps/tangle-dapp/data/liquifier/erc20Tokens.ts rename to apps/tangle-dapp/constants/liquidStaking/liquidStakingEvm.ts index bb3de41c2..895925fb2 100644 --- a/apps/tangle-dapp/data/liquifier/erc20Tokens.ts +++ b/apps/tangle-dapp/constants/liquidStaking/liquidStakingEvm.ts @@ -1,18 +1,18 @@ import { HexString } from '@polkadot/util/types'; -export type Erc20TokenDef = { - id: Erc20TokenId; +export type LsErc20TokenDef = { + id: LsErc20TokenId; name: string; address: HexString; liquifierAdapterAddress: HexString; }; -export enum Erc20TokenId { +export enum LsErc20TokenId { Chainlink, } -const ChainlinkTokenDef: Erc20TokenDef = { - id: Erc20TokenId.Chainlink, +const ChainlinkErc20TokenDef: LsErc20TokenDef = { + id: LsErc20TokenId.Chainlink, name: 'Chainlink', // TODO: Use Liquifier's testnet address if the environment is development. address: '0x514910771AF9Ca656af840dff83E8264EcF986CA', @@ -20,6 +20,6 @@ const ChainlinkTokenDef: Erc20TokenDef = { liquifierAdapterAddress: '0x', }; -export const ERC20_TOKEN_MAP: Record = { - [Erc20TokenId.Chainlink]: ChainlinkTokenDef, +export const LS_ERC20_TOKEN_MAP: Record = { + [LsErc20TokenId.Chainlink]: ChainlinkErc20TokenDef, }; diff --git a/apps/tangle-dapp/constants/liquidStaking.ts b/apps/tangle-dapp/constants/liquidStaking/liquidStakingParachain.ts similarity index 73% rename from apps/tangle-dapp/constants/liquidStaking.ts rename to apps/tangle-dapp/constants/liquidStaking/liquidStakingParachain.ts index 830c0a72f..844ef4ad5 100644 --- a/apps/tangle-dapp/constants/liquidStaking.ts +++ b/apps/tangle-dapp/constants/liquidStaking/liquidStakingParachain.ts @@ -6,28 +6,24 @@ import { BN } from '@polkadot/util'; import { TANGLE_TOKEN_DECIMALS } from '@webb-tools/dapp-config'; import { TANGLE_RESTAKING_PARACHAIN_LOCAL_DEV_NETWORK } from '@webb-tools/webb-ui-components/constants/networks'; -import { StaticAssetPath } from '.'; +import { StaticAssetPath } from '..'; -export enum LsProtocolId { +export enum LsParachainChainId { POLKADOT, PHALA, MOONBEAM, ASTAR, MANTA, TANGLE_RESTAKING_PARACHAIN, - CHAINLINK, } -export type Erc20ProtocolId = LsProtocolId.CHAINLINK; - -export enum LsToken { +export enum LsParachainToken { DOT = 'DOT', GLMR = 'GLMR', MANTA = 'MANTA', ASTAR = 'ASTR', PHALA = 'PHALA', TNT = 'TNT', - LINK = 'LINK', } // TODO: Temporary manual override until the Parachain types are updated. @@ -36,9 +32,9 @@ export type ParachainCurrency = TanglePrimitivesCurrencyTokenSymbol['type']; // | 'Tnt'; export type LsParachainProtocolDef = { - id: LsProtocolId; + id: LsParachainChainId; name: string; - token: LsToken; + token: LsParachainToken; logo: StaticAssetPath; networkName: string; currency: ParachainCurrency; @@ -47,9 +43,9 @@ export type LsParachainProtocolDef = { }; const POLKADOT: LsParachainProtocolDef = { - id: LsProtocolId.POLKADOT, + id: LsParachainChainId.POLKADOT, name: 'Polkadot', - token: LsToken.DOT, + token: LsParachainToken.DOT, logo: StaticAssetPath.LIQUID_STAKING_TOKEN_POLKADOT, networkName: 'Polkadot Mainnet', currency: 'Dot', @@ -58,9 +54,9 @@ const POLKADOT: LsParachainProtocolDef = { }; const PHALA: LsParachainProtocolDef = { - id: LsProtocolId.PHALA, + id: LsParachainChainId.PHALA, name: 'Phala', - token: LsToken.PHALA, + token: LsParachainToken.PHALA, logo: StaticAssetPath.LIQUID_STAKING_TOKEN_PHALA, networkName: 'Phala', currency: 'Pha', @@ -69,9 +65,9 @@ const PHALA: LsParachainProtocolDef = { }; const MOONBEAM: LsParachainProtocolDef = { - id: LsProtocolId.MOONBEAM, + id: LsParachainChainId.MOONBEAM, name: 'Moonbeam', - token: LsToken.GLMR, + token: LsParachainToken.GLMR, logo: StaticAssetPath.LIQUID_STAKING_TOKEN_GLIMMER, networkName: 'Moonbeam', // TODO: No currency entry for GLMR in the Tangle Primitives? @@ -81,9 +77,9 @@ const MOONBEAM: LsParachainProtocolDef = { }; const ASTAR: LsParachainProtocolDef = { - id: LsProtocolId.ASTAR, + id: LsParachainChainId.ASTAR, name: 'Astar', - token: LsToken.ASTAR, + token: LsParachainToken.ASTAR, logo: StaticAssetPath.LIQUID_STAKING_TOKEN_ASTAR, networkName: 'Astar', // TODO: No currency entry for ASTAR in the Tangle Primitives? @@ -93,9 +89,9 @@ const ASTAR: LsParachainProtocolDef = { }; const MANTA: LsParachainProtocolDef = { - id: LsProtocolId.MANTA, + id: LsParachainChainId.MANTA, name: 'Manta', - token: LsToken.MANTA, + token: LsParachainToken.MANTA, logo: StaticAssetPath.LIQUID_STAKING_TOKEN_MANTA, networkName: 'Manta', // TODO: No currency entry for ASTAR in the Tangle Primitives? @@ -105,9 +101,9 @@ const MANTA: LsParachainProtocolDef = { }; const TANGLE_RESTAKING_PARACHAIN: LsParachainProtocolDef = { - id: LsProtocolId.TANGLE_RESTAKING_PARACHAIN, + id: LsParachainChainId.TANGLE_RESTAKING_PARACHAIN, name: 'Tangle Parachain', - token: LsToken.TNT, + token: LsParachainToken.TNT, logo: StaticAssetPath.LIQUID_STAKING_TANGLE_LOGO, networkName: 'Tangle Parachain', currency: 'Bnc', @@ -115,27 +111,15 @@ const TANGLE_RESTAKING_PARACHAIN: LsParachainProtocolDef = { rpcEndpoint: TANGLE_RESTAKING_PARACHAIN_LOCAL_DEV_NETWORK.wsRpcEndpoint, }; -const CHAINLINK: LsParachainProtocolDef = { - id: LsProtocolId.CHAINLINK, - name: 'Chainlink', - token: LsToken.LINK, - networkName: 'Chainlink', - decimals: 18, - // TODO: Dummy data. Need to differentiate between EVM and Substrate chains. - currency: 'Asg', - logo: StaticAssetPath.LIQUID_STAKING_TANGLE_LOGO, - rpcEndpoint: 'wss://api.chain.link', -}; - -export const LS_CHAIN_MAP: Record = { - [LsProtocolId.POLKADOT]: POLKADOT, - [LsProtocolId.PHALA]: PHALA, - [LsProtocolId.MOONBEAM]: MOONBEAM, - [LsProtocolId.ASTAR]: ASTAR, - [LsProtocolId.MANTA]: MANTA, - [LsProtocolId.TANGLE_RESTAKING_PARACHAIN]: TANGLE_RESTAKING_PARACHAIN, - [LsProtocolId.CHAINLINK]: CHAINLINK, -}; +export const LS_CHAIN_MAP: Record = + { + [LsParachainChainId.POLKADOT]: POLKADOT, + [LsParachainChainId.PHALA]: PHALA, + [LsParachainChainId.MOONBEAM]: MOONBEAM, + [LsParachainChainId.ASTAR]: ASTAR, + [LsParachainChainId.MANTA]: MANTA, + [LsParachainChainId.TANGLE_RESTAKING_PARACHAIN]: TANGLE_RESTAKING_PARACHAIN, + }; export const LIQUID_STAKING_CHAINS: LsParachainProtocolDef[] = Object.values(LS_CHAIN_MAP); @@ -149,7 +133,7 @@ export const LST_PREFIX = 'tg'; export type Network = { name: string; endpoint: string; - tokenSymbol: LsToken; + tokenSymbol: LsParachainToken; chainType: NetworkType; }; @@ -171,7 +155,7 @@ export type LsSimpleParachainTimeUnit = { export type LsCardSearchParams = { amount: BN; - chainId: LsProtocolId; + chainId: LsParachainChainId; }; export enum LsSearchParamKey { diff --git a/apps/tangle-dapp/data/liquifier/liquifierAbi.ts b/apps/tangle-dapp/constants/liquidStaking/liquifierAbi.ts similarity index 98% rename from apps/tangle-dapp/data/liquifier/liquifierAbi.ts rename to apps/tangle-dapp/constants/liquidStaking/liquifierAbi.ts index 7dd50b532..9f6ae7c51 100644 --- a/apps/tangle-dapp/data/liquifier/liquifierAbi.ts +++ b/apps/tangle-dapp/constants/liquidStaking/liquifierAbi.ts @@ -1,3 +1,5 @@ +import { Abi } from 'viem'; + const liquifierAbi = [ { type: 'constructor', @@ -183,6 +185,6 @@ const liquifierAbi = [ }, ], }, -] as const; +] as const satisfies Abi; export default liquifierAbi; diff --git a/apps/tangle-dapp/data/liquifier/liquifierChainlinkAdapterAbi.ts b/apps/tangle-dapp/constants/liquidStaking/liquifierChainlinkAdapterAbi.ts similarity index 99% rename from apps/tangle-dapp/data/liquifier/liquifierChainlinkAdapterAbi.ts rename to apps/tangle-dapp/constants/liquidStaking/liquifierChainlinkAdapterAbi.ts index bc2c610fb..69631a021 100644 --- a/apps/tangle-dapp/data/liquifier/liquifierChainlinkAdapterAbi.ts +++ b/apps/tangle-dapp/constants/liquidStaking/liquifierChainlinkAdapterAbi.ts @@ -1,3 +1,5 @@ +import { Abi } from 'viem'; + const liquifierChainlinkAdapterAbi = [ { constant: false, @@ -339,6 +341,6 @@ const liquifierChainlinkAdapterAbi = [ name: 'DepositBufferedTokens', type: 'event', }, -] as const; +] as const satisfies Abi; export default liquifierChainlinkAdapterAbi; diff --git a/apps/tangle-dapp/data/liquidStaking/getValueOfTangleCurrency.ts b/apps/tangle-dapp/data/liquidStaking/getValueOfTangleCurrency.ts index 0a70dac63..3107f6581 100644 --- a/apps/tangle-dapp/data/liquidStaking/getValueOfTangleCurrency.ts +++ b/apps/tangle-dapp/data/liquidStaking/getValueOfTangleCurrency.ts @@ -5,7 +5,7 @@ import '@webb-tools/tangle-restaking-types'; import { TanglePrimitivesCurrencyCurrencyId } from '@polkadot/types/lookup'; import capitalize from 'lodash/capitalize'; -import { ParachainCurrency } from '../../constants/liquidStaking'; +import { ParachainCurrency } from '../../constants/liquidStaking/liquidStakingParachain'; const getValueOfTangleCurrency = ( tangleCurrencyId: TanglePrimitivesCurrencyCurrencyId, diff --git a/apps/tangle-dapp/data/liquidStaking/useDelegationsOccupiedStatus.ts b/apps/tangle-dapp/data/liquidStaking/useDelegationsOccupiedStatus.ts index c3ee974ef..d96e9643e 100644 --- a/apps/tangle-dapp/data/liquidStaking/useDelegationsOccupiedStatus.ts +++ b/apps/tangle-dapp/data/liquidStaking/useDelegationsOccupiedStatus.ts @@ -1,6 +1,6 @@ import { TANGLE_RESTAKING_PARACHAIN_LOCAL_DEV_NETWORK } from '@webb-tools/webb-ui-components/constants/networks'; -import { ParachainCurrency } from '../../constants/liquidStaking'; +import { ParachainCurrency } from '../../constants/liquidStaking/liquidStakingParachain'; import useApiRx from '../../hooks/useApiRx'; // TODO: Do a bit more research on what this signifies and means. Currently, it is only known that this is a requirement/check that may prevent further redeeming. diff --git a/apps/tangle-dapp/data/liquidStaking/useExchangeRate.ts b/apps/tangle-dapp/data/liquidStaking/useExchangeRate.ts index 1981de00d..fa2960772 100644 --- a/apps/tangle-dapp/data/liquidStaking/useExchangeRate.ts +++ b/apps/tangle-dapp/data/liquidStaking/useExchangeRate.ts @@ -4,7 +4,7 @@ import { useMemo } from 'react'; import { ParachainCurrency, LsParachainCurrencyKey, -} from '../../constants/liquidStaking'; +} from '../../constants/liquidStaking/liquidStakingParachain'; import useApiRx from '../../hooks/useApiRx'; import calculateBnRatio from '../../utils/calculateBnRatio'; diff --git a/apps/tangle-dapp/data/liquidStaking/useLiquidStakingItems.ts b/apps/tangle-dapp/data/liquidStaking/useLiquidStakingItems.ts index 51f139203..6586c11af 100644 --- a/apps/tangle-dapp/data/liquidStaking/useLiquidStakingItems.ts +++ b/apps/tangle-dapp/data/liquidStaking/useLiquidStakingItems.ts @@ -1,7 +1,10 @@ import { BN_ZERO } from '@polkadot/util'; import { useCallback, useEffect, useMemo, useState } from 'react'; -import { LS_CHAIN_MAP, LsProtocolId } from '../../constants/liquidStaking'; +import { + LS_CHAIN_MAP, + LsParachainChainId, +} from '../../constants/liquidStaking/liquidStakingParachain'; import useLocalStorage, { LocalStorageKey } from '../../hooks/useLocalStorage'; import { Collator, @@ -24,7 +27,7 @@ import { fetchVaultsAndStakePools, } from './helper'; -const useLiquidStakingItems = (selectedChain: LsProtocolId) => { +const useLiquidStakingItems = (selectedChain: LsParachainChainId) => { const { setWithPreviousValue: setLiquidStakingTableData } = useLocalStorage( LocalStorageKey.LIQUID_STAKING_TABLE_DATA, ); @@ -38,7 +41,7 @@ const useLiquidStakingItems = (selectedChain: LsProtocolId) => { const dataType = useMemo(() => getDataType(selectedChain), [selectedChain]); const fetchData = useCallback( - async (chain: LsProtocolId) => { + async (chain: LsParachainChainId) => { const endpoint = LS_CHAIN_MAP[chain]?.rpcEndpoint; if (!endpoint) { @@ -51,30 +54,30 @@ const useLiquidStakingItems = (selectedChain: LsProtocolId) => { []; switch (chain) { - case LsProtocolId.POLKADOT: + case LsParachainChainId.POLKADOT: fetchedItems = await getValidators(endpoint); break; - case LsProtocolId.ASTAR: + case LsParachainChainId.ASTAR: fetchedItems = await getDapps(endpoint); break; - case LsProtocolId.PHALA: + case LsParachainChainId.PHALA: fetchedItems = await getVaultsAndStakePools(endpoint); break; - case LsProtocolId.MOONBEAM: + case LsParachainChainId.MOONBEAM: fetchedItems = await getCollators( endpoint, - LsProtocolId.MOONBEAM, + LsParachainChainId.MOONBEAM, 'https://stakeglmr.com/', ); break; - case LsProtocolId.MANTA: + case LsParachainChainId.MANTA: fetchedItems = await getCollators( endpoint, - LsProtocolId.MANTA, + LsParachainChainId.MANTA, 'https://manta.subscan.io/account/', ); break; @@ -110,18 +113,18 @@ const useLiquidStakingItems = (selectedChain: LsProtocolId) => { export default useLiquidStakingItems; -const getDataType = (chain: LsProtocolId) => { +const getDataType = (chain: LsParachainChainId) => { switch (chain) { - case LsProtocolId.MANTA: + case LsParachainChainId.MANTA: return LiquidStakingItem.COLLATOR; - case LsProtocolId.MOONBEAM: + case LsParachainChainId.MOONBEAM: return LiquidStakingItem.COLLATOR; - case LsProtocolId.TANGLE_RESTAKING_PARACHAIN: - case LsProtocolId.POLKADOT: + case LsParachainChainId.TANGLE_RESTAKING_PARACHAIN: + case LsParachainChainId.POLKADOT: return LiquidStakingItem.VALIDATOR; - case LsProtocolId.PHALA: + case LsParachainChainId.PHALA: return LiquidStakingItem.VAULT_OR_STAKE_POOL; - case LsProtocolId.ASTAR: + case LsParachainChainId.ASTAR: return LiquidStakingItem.DAPP; default: return LiquidStakingItem.VALIDATOR; @@ -157,7 +160,7 @@ const getValidators = async (endpoint: string): Promise => { totalValueStaked: totalValueStaked || BN_ZERO, validatorAPY: 0, validatorCommission: commission || BN_ZERO, - chainId: LsProtocolId.POLKADOT, + chainId: LsParachainChainId.POLKADOT, chainDecimals, chainTokenSymbol, itemType: LiquidStakingItem.VALIDATOR, @@ -204,7 +207,7 @@ const getDapps = async (endpoint: string): Promise => { dappContractType: dappInfo ? dappInfo.contractType || '' : '', commission: BN_ZERO, totalValueStaked: totalValueStaked || BN_ZERO, - chainId: LsProtocolId.ASTAR, + chainId: LsParachainChainId.ASTAR, chainDecimals, chainTokenSymbol, itemType: LiquidStakingItem.DAPP, @@ -234,7 +237,7 @@ const getVaultsAndStakePools = async ( vaultOrStakePoolName: val.id, commission: val.commission, totalValueStaked: val.totalValueStaked, - chainId: LsProtocolId.PHALA, + chainId: LsParachainChainId.PHALA, chainDecimals, chainTokenSymbol, type, @@ -246,7 +249,7 @@ const getVaultsAndStakePools = async ( const getCollators = async ( endpoint: string, - chainId: LsProtocolId, + chainId: LsParachainChainId, href: string, ): Promise => { const [ @@ -269,9 +272,9 @@ const getCollators = async ( let collatorExternalLink = ''; - if (chainId === LsProtocolId.MOONBEAM) { + if (chainId === LsParachainChainId.MOONBEAM) { collatorExternalLink = href; - } else if (chainId === LsProtocolId.MANTA) { + } else if (chainId === LsParachainChainId.MANTA) { collatorExternalLink = href + collator; } diff --git a/apps/tangle-dapp/data/liquidStaking/store.ts b/apps/tangle-dapp/data/liquidStaking/useLiquidStakingStore.ts similarity index 72% rename from apps/tangle-dapp/data/liquidStaking/store.ts rename to apps/tangle-dapp/data/liquidStaking/useLiquidStakingStore.ts index e9e2b9ebd..088c7ebbf 100644 --- a/apps/tangle-dapp/data/liquidStaking/store.ts +++ b/apps/tangle-dapp/data/liquidStaking/useLiquidStakingStore.ts @@ -1,9 +1,9 @@ import { create } from 'zustand'; -import { LsProtocolId } from '../../constants/liquidStaking'; +import { LsParachainChainId } from '../../constants/liquidStaking/liquidStakingParachain'; type State = { - selectedChainId: LsProtocolId; + selectedChainId: LsParachainChainId; selectedItems: Set; }; @@ -15,7 +15,7 @@ type Actions = { type Store = State & Actions; export const useLiquidStakingStore = create((set) => ({ - selectedChainId: LsProtocolId.TANGLE_RESTAKING_PARACHAIN, + selectedChainId: LsParachainChainId.TANGLE_RESTAKING_PARACHAIN, selectedItems: new Set(), setSelectedChainId: (selectedChainId) => set({ selectedChainId }), setSelectedItems: (selectedItems) => set({ selectedItems }), diff --git a/apps/tangle-dapp/data/liquidStaking/useLstRebondTx.ts b/apps/tangle-dapp/data/liquidStaking/useLstRebondTx.ts index 7bc4f46e4..3a49bc29a 100644 --- a/apps/tangle-dapp/data/liquidStaking/useLstRebondTx.ts +++ b/apps/tangle-dapp/data/liquidStaking/useLstRebondTx.ts @@ -4,7 +4,7 @@ import { TxName } from '../../constants'; import { ParachainCurrency, LsParachainCurrencyKey, -} from '../../constants/liquidStaking'; +} from '../../constants/liquidStaking/liquidStakingParachain'; import { useSubstrateTxWithNotification } from '../../hooks/useSubstrateTx'; import optimizeTxBatch from '../../utils/optimizeTxBatch'; diff --git a/apps/tangle-dapp/data/liquidStaking/useLstUnlockRequests.ts b/apps/tangle-dapp/data/liquidStaking/useLstUnlockRequests.ts index 49e71027c..b7a48c05c 100644 --- a/apps/tangle-dapp/data/liquidStaking/useLstUnlockRequests.ts +++ b/apps/tangle-dapp/data/liquidStaking/useLstUnlockRequests.ts @@ -14,7 +14,7 @@ import { map } from 'rxjs'; import { ParachainCurrency, LsSimpleParachainTimeUnit, -} from '../../constants/liquidStaking'; +} from '../../constants/liquidStaking/liquidStakingParachain'; import useApiRx from '../../hooks/useApiRx'; import useSubstrateAddress from '../../hooks/useSubstrateAddress'; import compareSubstrateAddresses from '../../utils/compareSubstrateAddresses'; diff --git a/apps/tangle-dapp/data/liquidStaking/useLstWithdrawRedeemTx.ts b/apps/tangle-dapp/data/liquidStaking/useLstWithdrawRedeemTx.ts index a3261bd74..3240adf08 100644 --- a/apps/tangle-dapp/data/liquidStaking/useLstWithdrawRedeemTx.ts +++ b/apps/tangle-dapp/data/liquidStaking/useLstWithdrawRedeemTx.ts @@ -4,7 +4,7 @@ import { TxName } from '../../constants'; import { ParachainCurrency, LsParachainCurrencyKey, -} from '../../constants/liquidStaking'; +} from '../../constants/liquidStaking/liquidStakingParachain'; import { useSubstrateTxWithNotification } from '../../hooks/useSubstrateTx'; import optimizeTxBatch from '../../utils/optimizeTxBatch'; diff --git a/apps/tangle-dapp/data/liquidStaking/useMintTx.ts b/apps/tangle-dapp/data/liquidStaking/useMintTx.ts index 811d0d8bc..7ad5d229e 100644 --- a/apps/tangle-dapp/data/liquidStaking/useMintTx.ts +++ b/apps/tangle-dapp/data/liquidStaking/useMintTx.ts @@ -10,7 +10,7 @@ import { TxName } from '../../constants'; import { ParachainCurrency, LsParachainCurrencyKey, -} from '../../constants/liquidStaking'; +} from '../../constants/liquidStaking/liquidStakingParachain'; import { useSubstrateTxWithNotification } from '../../hooks/useSubstrateTx'; export type MintTxContext = { diff --git a/apps/tangle-dapp/data/liquidStaking/useOngoingTimeUnits.ts b/apps/tangle-dapp/data/liquidStaking/useOngoingTimeUnits.ts index 58a7acba5..232be501c 100644 --- a/apps/tangle-dapp/data/liquidStaking/useOngoingTimeUnits.ts +++ b/apps/tangle-dapp/data/liquidStaking/useOngoingTimeUnits.ts @@ -5,7 +5,7 @@ import { useCallback, useMemo } from 'react'; import { ParachainCurrency, LsSimpleParachainTimeUnit, -} from '../../constants/liquidStaking'; +} from '../../constants/liquidStaking/liquidStakingParachain'; import useApiRx from '../../hooks/useApiRx'; import tangleTimeUnitToSimpleInstance from '../../utils/liquidStaking/tangleTimeUnitToSimpleInstance'; import getValueOfTangleCurrency from './getValueOfTangleCurrency'; diff --git a/apps/tangle-dapp/data/liquidStaking/useParachainBalances.ts b/apps/tangle-dapp/data/liquidStaking/useParachainBalances.ts index b8664a2bc..05e5bd565 100644 --- a/apps/tangle-dapp/data/liquidStaking/useParachainBalances.ts +++ b/apps/tangle-dapp/data/liquidStaking/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 { LsToken } from '../../constants/liquidStaking'; +import { LsParachainToken } from '../../constants/liquidStaking/liquidStakingParachain'; import useApiRx from '../../hooks/useApiRx'; import useSubstrateAddress from '../../hooks/useSubstrateAddress'; -import isLiquidStakingToken from '../../utils/liquidStaking/isLiquidStakingToken'; +import isLsParachainToken from '../../utils/liquidStaking/isLsParachainToken'; const useParachainBalances = () => { const activeSubstrateAddress = useSubstrateAddress(); @@ -35,8 +35,8 @@ const useParachainBalances = () => { return null; } - const nativeBalances = new Map(); - const liquidBalances = new Map(); + const nativeBalances = new Map(); + const liquidBalances = new Map(); for (const encodedBalance of rawBalances) { // TODO: Need proper typing, ideally linked to Restaking Parachain's types. This is currently very hacky. @@ -51,7 +51,7 @@ const useParachainBalances = () => { // Irrelevant entry, skip. if ( entryTokenValue === undefined || - !isLiquidStakingToken(entryTokenValue) + !isLsParachainToken(entryTokenValue) ) { continue; } diff --git a/apps/tangle-dapp/data/liquidStaking/useRedeemTx.ts b/apps/tangle-dapp/data/liquidStaking/useRedeemTx.ts index 67be68dcf..b9c2de799 100644 --- a/apps/tangle-dapp/data/liquidStaking/useRedeemTx.ts +++ b/apps/tangle-dapp/data/liquidStaking/useRedeemTx.ts @@ -9,7 +9,7 @@ import { TxName } from '../../constants'; import { ParachainCurrency, LsParachainCurrencyKey, -} from '../../constants/liquidStaking'; +} from '../../constants/liquidStaking/liquidStakingParachain'; import { useSubstrateTxWithNotification } from '../../hooks/useSubstrateTx'; export type RedeemTxContext = { diff --git a/apps/tangle-dapp/data/liquidStaking/useTokenUnlockDurations.ts b/apps/tangle-dapp/data/liquidStaking/useTokenUnlockDurations.ts index 91f34a192..4bd65b667 100644 --- a/apps/tangle-dapp/data/liquidStaking/useTokenUnlockDurations.ts +++ b/apps/tangle-dapp/data/liquidStaking/useTokenUnlockDurations.ts @@ -8,7 +8,7 @@ import { useCallback, useMemo } from 'react'; import { LsSimpleParachainTimeUnit, ParachainCurrency, -} from '../../constants/liquidStaking'; +} from '../../constants/liquidStaking/liquidStakingParachain'; import useApiRx from '../../hooks/useApiRx'; import tangleTimeUnitToSimpleInstance from '../../utils/liquidStaking/tangleTimeUnitToSimpleInstance'; import getValueOfTangleCurrency from './getValueOfTangleCurrency'; diff --git a/apps/tangle-dapp/data/liquidStaking/useTokenUnlockLedger.ts b/apps/tangle-dapp/data/liquidStaking/useTokenUnlockLedger.ts index f5038ae4f..4309d9364 100644 --- a/apps/tangle-dapp/data/liquidStaking/useTokenUnlockLedger.ts +++ b/apps/tangle-dapp/data/liquidStaking/useTokenUnlockLedger.ts @@ -5,7 +5,7 @@ import '@webb-tools/tangle-restaking-types'; import { TANGLE_RESTAKING_PARACHAIN_LOCAL_DEV_NETWORK } from '@webb-tools/webb-ui-components/constants/networks'; import { useCallback, useMemo } from 'react'; -import { LsParachainCurrencyKey } from '../../constants/liquidStaking'; +import { LsParachainCurrencyKey } from '../../constants/liquidStaking/liquidStakingParachain'; import useApiRx from '../../hooks/useApiRx'; import useSubstrateAddress from '../../hooks/useSubstrateAddress'; import tangleTimeUnitToSimpleInstance from '../../utils/liquidStaking/tangleTimeUnitToSimpleInstance'; diff --git a/apps/tangle-dapp/data/liquifier/useLiquifierDeposit.ts b/apps/tangle-dapp/data/liquifier/useLiquifierDeposit.ts index 43a372459..4b47934f7 100644 --- a/apps/tangle-dapp/data/liquifier/useLiquifierDeposit.ts +++ b/apps/tangle-dapp/data/liquifier/useLiquifierDeposit.ts @@ -4,10 +4,13 @@ import { useCallback } from 'react'; import { erc20Abi } from 'viem'; import { TxName } from '../../constants'; +import { + LS_ERC20_TOKEN_MAP, + LsErc20TokenId, +} from '../../constants/liquidStaking/liquidStakingEvm'; +import liquifierAbi from '../../constants/liquidStaking/liquifierAbi'; import useEvmAddress20 from '../../hooks/useEvmAddress'; import useTxNotification from '../../hooks/useTxNotification'; -import { ERC20_TOKEN_MAP, Erc20TokenId } from './erc20Tokens'; -import liquifierAbi from './liquifierAbi'; import useContract from './useContract'; /** @@ -50,7 +53,7 @@ const useLiquifierDeposit = () => { activeEvmAddress20 !== null; const deposit = useCallback( - async (token: Erc20TokenId, amount: BN) => { + async (token: LsErc20TokenId, amount: BN) => { // TODO: Should the user balance check be done here or assume that the consumer of the hook will handle that? assert( @@ -58,7 +61,7 @@ const useLiquifierDeposit = () => { 'Should not be able to call this function if the requirements are not ready yet', ); - const tokenDef = ERC20_TOKEN_MAP[token]; + const tokenDef = LS_ERC20_TOKEN_MAP[token]; notifyApproveProcessing(); diff --git a/apps/tangle-dapp/hooks/useEvmPrecompileAbiCall.ts b/apps/tangle-dapp/hooks/useEvmPrecompileAbiCall.ts index 16c047c45..b9f8063c4 100644 --- a/apps/tangle-dapp/hooks/useEvmPrecompileAbiCall.ts +++ b/apps/tangle-dapp/hooks/useEvmPrecompileAbiCall.ts @@ -1,7 +1,6 @@ import { BN } from '@polkadot/util'; import { HexString } from '@polkadot/util/types'; import { PromiseOrT } from '@webb-tools/abstract-api-provider'; -import { AddressType } from '@webb-tools/dapp-config/types'; import { useCallback, useEffect, useState } from 'react'; import { simulateContract, diff --git a/apps/tangle-dapp/types/liquidStaking.ts b/apps/tangle-dapp/types/liquidStaking.ts index 20ac9f88a..736f98216 100644 --- a/apps/tangle-dapp/types/liquidStaking.ts +++ b/apps/tangle-dapp/types/liquidStaking.ts @@ -1,13 +1,13 @@ import { BN } from '@polkadot/util'; -import { LsProtocolId } from '../constants/liquidStaking'; +import { LsParachainChainId } from '../constants/liquidStaking/liquidStakingParachain'; // All chains export type StakingItem = { id: string; // address - Validator, contract address - DAPP, pool/vault ID - VaultOrStakePool totalValueStaked: BN; minimumStake?: BN; - chainId: LsProtocolId; + chainId: LsParachainChainId; chainDecimals: number; chainTokenSymbol: string; itemType: LiquidStakingItem; diff --git a/apps/tangle-dapp/utils/liquidStaking/isLiquidStakingToken.ts b/apps/tangle-dapp/utils/liquidStaking/isLiquidStakingToken.ts deleted file mode 100644 index cc7b7555c..000000000 --- a/apps/tangle-dapp/utils/liquidStaking/isLiquidStakingToken.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { LsToken } from '../../constants/liquidStaking'; - -function isLiquidStakingToken(tokenSymbol: string): tokenSymbol is LsToken { - return Object.values(LsToken).includes(tokenSymbol as LsToken); -} - -export default isLiquidStakingToken; diff --git a/apps/tangle-dapp/utils/liquidStaking/isLsParachainToken.ts b/apps/tangle-dapp/utils/liquidStaking/isLsParachainToken.ts new file mode 100644 index 000000000..376c0e38f --- /dev/null +++ b/apps/tangle-dapp/utils/liquidStaking/isLsParachainToken.ts @@ -0,0 +1,11 @@ +import { LsParachainToken } from '../../constants/liquidStaking/liquidStakingParachain'; + +function isLsParachainToken( + tokenSymbol: string, +): tokenSymbol is LsParachainToken { + return Object.values(LsParachainToken).includes( + tokenSymbol as LsParachainToken, + ); +} + +export default isLsParachainToken; diff --git a/apps/tangle-dapp/utils/liquidStaking/stringifyTimeUnit.ts b/apps/tangle-dapp/utils/liquidStaking/stringifyTimeUnit.ts index d9bf52cf8..64c60a8bb 100644 --- a/apps/tangle-dapp/utils/liquidStaking/stringifyTimeUnit.ts +++ b/apps/tangle-dapp/utils/liquidStaking/stringifyTimeUnit.ts @@ -1,4 +1,4 @@ -import { LsSimpleParachainTimeUnit } from '../../constants/liquidStaking'; +import { LsSimpleParachainTimeUnit } from '../../constants/liquidStaking/liquidStakingParachain'; const stringifyTimeUnit = ( timeUnit: LsSimpleParachainTimeUnit, diff --git a/apps/tangle-dapp/utils/liquidStaking/tangleTimeUnitToSimpleInstance.ts b/apps/tangle-dapp/utils/liquidStaking/tangleTimeUnitToSimpleInstance.ts index 07bfbd82b..eb6005172 100644 --- a/apps/tangle-dapp/utils/liquidStaking/tangleTimeUnitToSimpleInstance.ts +++ b/apps/tangle-dapp/utils/liquidStaking/tangleTimeUnitToSimpleInstance.ts @@ -1,6 +1,6 @@ import { TanglePrimitivesTimeUnit } from '@polkadot/types/lookup'; -import { LsSimpleParachainTimeUnit } from '../../constants/liquidStaking'; +import { LsSimpleParachainTimeUnit } from '../../constants/liquidStaking/liquidStakingParachain'; import getValueOfTangleTimeUnit from './getValueOfTangleTimeUnit'; const tangleTimeUnitToSimpleInstance = ( From 6f16172551156e3d6cc307e891e94d369479ef69 Mon Sep 17 00:00:00 2001 From: yurixander <101931215+yurixander@users.noreply.github.com> Date: Tue, 13 Aug 2024 16:57:32 -0400 Subject: [PATCH 10/60] feat(tangle-dapp): Implement `useLiquifierTgBalance` hook --- .../LiquidStakingSelectionTable.tsx | 2 +- .../stakeAndUnstake/LiquidStakeCard.tsx | 8 +- .../liquidStaking/liquidStakingEvm.ts | 50 +++ ...nkAdapterAbi.ts => liquifierAdapterAbi.ts} | 4 +- .../liquidStaking/liquifierTgTokenAbi.ts | 291 ++++++++++++++++++ .../data/liquifier/useLiquifierBalances.ts | 14 - .../liquifier/useLiquifierErc20Balance.ts | 35 +++ .../data/liquifier/useLiquifierTgBalance.ts | 34 ++ 8 files changed, 417 insertions(+), 21 deletions(-) rename apps/tangle-dapp/constants/liquidStaking/{liquifierChainlinkAdapterAbi.ts => liquifierAdapterAbi.ts} (98%) create mode 100644 apps/tangle-dapp/constants/liquidStaking/liquifierTgTokenAbi.ts delete mode 100644 apps/tangle-dapp/data/liquifier/useLiquifierBalances.ts create mode 100644 apps/tangle-dapp/data/liquifier/useLiquifierErc20Balance.ts create mode 100644 apps/tangle-dapp/data/liquifier/useLiquifierTgBalance.ts diff --git a/apps/tangle-dapp/components/LiquidStaking/LiquidStakingSelectionTable.tsx b/apps/tangle-dapp/components/LiquidStaking/LiquidStakingSelectionTable.tsx index dfd8476e7..a635925dc 100644 --- a/apps/tangle-dapp/components/LiquidStaking/LiquidStakingSelectionTable.tsx +++ b/apps/tangle-dapp/components/LiquidStaking/LiquidStakingSelectionTable.tsx @@ -24,8 +24,8 @@ import { } from '@webb-tools/webb-ui-components'; import { useEffect, useMemo, useRef, useState } from 'react'; -import { useLiquidStakingStore } from '../../data/liquidStaking/useLiquidStakingStore'; import useLiquidStakingItems from '../../data/liquidStaking/useLiquidStakingItems'; +import { useLiquidStakingStore } from '../../data/liquidStaking/useLiquidStakingStore'; import { useLiquidStakingSelectionTableColumns } from '../../hooks/LiquidStaking/useLiquidStakingSelectionTableColumns'; import { LiquidStakingItem, diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakeCard.tsx b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakeCard.tsx index 55924a960..e5f432835 100644 --- a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakeCard.tsx +++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakeCard.tsx @@ -18,15 +18,15 @@ import React, { FC, useCallback, useMemo } from 'react'; import { z } from 'zod'; import { - LsSearchParamKey, - LST_PREFIX, LS_CHAIN_MAP, LsParachainChainId, + LsSearchParamKey, + LST_PREFIX, } from '../../../constants/liquidStaking/liquidStakingParachain'; -import { useLiquidStakingStore } from '../../../data/liquidStaking/useLiquidStakingStore'; import useExchangeRate, { ExchangeRateType, } from '../../../data/liquidStaking/useExchangeRate'; +import { useLiquidStakingStore } from '../../../data/liquidStaking/useLiquidStakingStore'; import useMintTx from '../../../data/liquidStaking/useMintTx'; import useParachainBalances from '../../../data/liquidStaking/useParachainBalances'; import useApi from '../../../hooks/useApi'; @@ -58,7 +58,7 @@ const LiquidStakeCard: FC = () => { useSearchParamSync({ key: LsSearchParamKey.CHAIN_ID, value: selectedChainId, - parse: (value) => z.nativeEnum(ParachainChainId).parse(parseInt(value)), + parse: (value) => z.nativeEnum(LsParachainChainId).parse(parseInt(value)), stringify: (value) => value.toString(), setValue: setSelectedChainId, }); diff --git a/apps/tangle-dapp/constants/liquidStaking/liquidStakingEvm.ts b/apps/tangle-dapp/constants/liquidStaking/liquidStakingEvm.ts index 895925fb2..24bc9e39b 100644 --- a/apps/tangle-dapp/constants/liquidStaking/liquidStakingEvm.ts +++ b/apps/tangle-dapp/constants/liquidStaking/liquidStakingEvm.ts @@ -3,23 +3,73 @@ import { HexString } from '@polkadot/util/types'; export type LsErc20TokenDef = { id: LsErc20TokenId; name: string; + token: LsErc20Token; address: HexString; liquifierAdapterAddress: HexString; + liquifierTgTokenAddress: HexString; }; export enum LsErc20TokenId { Chainlink, + TheGraph, + Livepeer, + Polygon, +} + +export enum LsErc20Token { + LINK, + GRT, + LPT, + POL, } const ChainlinkErc20TokenDef: LsErc20TokenDef = { id: LsErc20TokenId.Chainlink, name: 'Chainlink', + token: LsErc20Token.LINK, // TODO: Use Liquifier's testnet address if the environment is development. address: '0x514910771AF9Ca656af840dff83E8264EcF986CA', // TODO: Use the actual Chainlink Liquifier Adapter address. This is likely deployed to a testnet (Tenderly?). liquifierAdapterAddress: '0x', + liquifierTgTokenAddress: '0x', +}; + +const TheGraphErc20TokenDef: LsErc20TokenDef = { + id: LsErc20TokenId.TheGraph, + name: 'The Graph', + token: LsErc20Token.GRT, + // TODO: Use Liquifier's testnet address if the environment is development. + address: '0xc944E90C64B2c07662A292be6244BDf05Cda44a7', + // TODO: Use the actual Chainlink Liquifier Adapter address. This is likely deployed to a testnet (Tenderly?). + liquifierAdapterAddress: '0x', + liquifierTgTokenAddress: '0x', +}; + +const LivepeerErc20TokenDef: LsErc20TokenDef = { + id: LsErc20TokenId.Livepeer, + name: 'Livepeer', + token: LsErc20Token.LPT, + // TODO: Use Liquifier's testnet address if the environment is development. + address: '0x58b6A8A3302369DAEc383334672404Ee733aB239', + // TODO: Use the actual Chainlink Liquifier Adapter address. This is likely deployed to a testnet (Tenderly?). + liquifierAdapterAddress: '0x', + liquifierTgTokenAddress: '0x', +}; + +const PolygonErc20TokenDef: LsErc20TokenDef = { + id: LsErc20TokenId.Polygon, + name: 'Polygon', + token: LsErc20Token.POL, + // TODO: Use Liquifier's testnet address if the environment is development. + address: '0x0D500B1d8E8eF31E21C99d1Db9A6444d3ADf1270', + // TODO: Use the actual Chainlink Liquifier Adapter address. This is likely deployed to a testnet (Tenderly?). + liquifierAdapterAddress: '0x', + liquifierTgTokenAddress: '0x', }; export const LS_ERC20_TOKEN_MAP: Record = { [LsErc20TokenId.Chainlink]: ChainlinkErc20TokenDef, + [LsErc20TokenId.TheGraph]: TheGraphErc20TokenDef, + [LsErc20TokenId.Livepeer]: LivepeerErc20TokenDef, + [LsErc20TokenId.Polygon]: PolygonErc20TokenDef, }; diff --git a/apps/tangle-dapp/constants/liquidStaking/liquifierChainlinkAdapterAbi.ts b/apps/tangle-dapp/constants/liquidStaking/liquifierAdapterAbi.ts similarity index 98% rename from apps/tangle-dapp/constants/liquidStaking/liquifierChainlinkAdapterAbi.ts rename to apps/tangle-dapp/constants/liquidStaking/liquifierAdapterAbi.ts index 69631a021..198082b11 100644 --- a/apps/tangle-dapp/constants/liquidStaking/liquifierChainlinkAdapterAbi.ts +++ b/apps/tangle-dapp/constants/liquidStaking/liquifierAdapterAbi.ts @@ -1,6 +1,6 @@ import { Abi } from 'viem'; -const liquifierChainlinkAdapterAbi = [ +const liquifierAdapterAbi = [ { constant: false, inputs: [ @@ -343,4 +343,4 @@ const liquifierChainlinkAdapterAbi = [ }, ] as const satisfies Abi; -export default liquifierChainlinkAdapterAbi; +export default liquifierAdapterAbi; diff --git a/apps/tangle-dapp/constants/liquidStaking/liquifierTgTokenAbi.ts b/apps/tangle-dapp/constants/liquidStaking/liquifierTgTokenAbi.ts new file mode 100644 index 000000000..929c301c0 --- /dev/null +++ b/apps/tangle-dapp/constants/liquidStaking/liquifierTgTokenAbi.ts @@ -0,0 +1,291 @@ +import { Abi } from 'viem'; + +const liquifierTgTokenAbi = [ + { + inputs: [], + name: 'decimals', + outputs: [ + { + internalType: 'uint8', + name: '', + type: 'uint8', + }, + ], + stateMutability: 'pure', + type: 'function', + }, + { + inputs: [], + name: 'name', + outputs: [ + { + internalType: 'string', + name: '', + type: 'string', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'symbol', + outputs: [ + { + internalType: 'string', + name: '', + type: 'string', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'shares', + type: 'uint256', + }, + ], + name: 'convertToAssets', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'assets', + type: 'uint256', + }, + ], + name: 'convertToShares', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'account', + type: 'address', + }, + ], + name: 'balanceOf', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'totalSupply', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'owner', + type: 'address', + }, + ], + name: 'nonces', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'spender', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'approve', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'transfer', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'owner', + type: 'address', + }, + { + internalType: 'address', + name: 'spender', + type: 'address', + }, + ], + name: 'allowance', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'from', + type: 'address', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'transferFrom', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'owner', + type: 'address', + }, + { + internalType: 'address', + name: 'spender', + type: 'address', + }, + { + internalType: 'uint256', + name: 'value', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + { + internalType: 'uint8', + name: 'v', + type: 'uint8', + }, + { + internalType: 'bytes32', + name: 'r', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 's', + type: 'bytes32', + }, + ], + name: 'permit', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'DOMAIN_SEPARATOR', + outputs: [ + { + internalType: 'bytes32', + name: '', + type: 'bytes32', + }, + ], + stateMutability: 'view', + type: 'function', + }, +] as const satisfies Abi; + +export default liquifierTgTokenAbi; diff --git a/apps/tangle-dapp/data/liquifier/useLiquifierBalances.ts b/apps/tangle-dapp/data/liquifier/useLiquifierBalances.ts deleted file mode 100644 index fe4243c15..000000000 --- a/apps/tangle-dapp/data/liquifier/useLiquifierBalances.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { useMemo } from 'react'; -import { createPublicClient, http } from 'viem'; -import { mainnet } from 'viem/chains'; - -const useLiquifierBalances = () => { - const client = useMemo(() => { - return createPublicClient({ - chain: mainnet, - transport: http(), - }); - }, []); -}; - -export default useLiquifierBalances; diff --git a/apps/tangle-dapp/data/liquifier/useLiquifierErc20Balance.ts b/apps/tangle-dapp/data/liquifier/useLiquifierErc20Balance.ts new file mode 100644 index 000000000..967dc9afe --- /dev/null +++ b/apps/tangle-dapp/data/liquifier/useLiquifierErc20Balance.ts @@ -0,0 +1,35 @@ +import { BN } from '@polkadot/util'; +import { useEffect, useState } from 'react'; +import { erc20Abi } from 'viem'; + +import { + LS_ERC20_TOKEN_MAP, + LsErc20TokenId, +} from '../../constants/liquidStaking/liquidStakingEvm'; +import useEvmAddress20 from '../../hooks/useEvmAddress'; +import useContract from './useContract'; + +const useLiquifierErc20Balance = (tokenId: LsErc20TokenId): BN | null => { + const activeEvmAddress20 = useEvmAddress20(); + const [balance, setBalance] = useState(null); + const { read } = useContract(erc20Abi); + + // TODO: This only loads the balance once. Make it so it updates every few seconds that way the it responds to any balance changes that may occur, not just when loading the site initially. Like a subscription. + useEffect(() => { + if (activeEvmAddress20 === null || read === null) { + return; + } + + const tokenDef = LS_ERC20_TOKEN_MAP[tokenId]; + + read({ + address: tokenDef.address, + functionName: 'balanceOf', + args: [activeEvmAddress20], + }).then((result) => setBalance(new BN(result.toString()))); + }, [activeEvmAddress20, read, tokenId]); + + return balance; +}; + +export default useLiquifierErc20Balance; diff --git a/apps/tangle-dapp/data/liquifier/useLiquifierTgBalance.ts b/apps/tangle-dapp/data/liquifier/useLiquifierTgBalance.ts new file mode 100644 index 000000000..e5a64badb --- /dev/null +++ b/apps/tangle-dapp/data/liquifier/useLiquifierTgBalance.ts @@ -0,0 +1,34 @@ +import { BN } from '@polkadot/util'; +import { useEffect, useState } from 'react'; + +import { + LS_ERC20_TOKEN_MAP, + LsErc20TokenId, +} from '../../constants/liquidStaking/liquidStakingEvm'; +import liquifierTgTokenAbi from '../../constants/liquidStaking/liquifierTgTokenAbi'; +import useEvmAddress20 from '../../hooks/useEvmAddress'; +import useContract from './useContract'; + +const useLiquifierTgBalance = (tokenId: LsErc20TokenId) => { + const activeEvmAddress20 = useEvmAddress20(); + const { read } = useContract(liquifierTgTokenAbi); + const [balance, setBalance] = useState(null); + + useEffect(() => { + if (activeEvmAddress20 === null || read === null) { + return; + } + + const tokenDef = LS_ERC20_TOKEN_MAP[tokenId]; + + read({ + address: tokenDef.liquifierTgTokenAddress, + functionName: 'balanceOf', + args: [activeEvmAddress20], + }).then((result) => setBalance(new BN(result.toString()))); + }, [activeEvmAddress20, read, tokenId]); + + return balance; +}; + +export default useLiquifierTgBalance; From f857b6cf65f66ce7c64503e958669779392b5ddf Mon Sep 17 00:00:00 2001 From: yurixander <101931215+yurixander@users.noreply.github.com> Date: Tue, 13 Aug 2024 20:45:51 -0400 Subject: [PATCH 11/60] refactor(tangle-dapp): Integrate ERC20 token defs with parachain chain defs --- .../app/liquid-staking/[tokenSymbol]/page.tsx | 2 +- apps/tangle-dapp/app/liquid-staking/page.tsx | 12 +- .../components/Breadcrumbs/utils.tsx | 2 +- .../stakeAndUnstake/ChainLogo.tsx | 20 +-- .../ExchangeRateDetailItem.tsx | 9 +- .../stakeAndUnstake/LiquidStakeCard.tsx | 50 +++---- .../stakeAndUnstake/LiquidStakingInput.tsx | 26 ++-- .../LiquidStakingTokenItem.tsx | 10 +- .../stakeAndUnstake/LiquidUnstakeCard.tsx | 57 ++++---- .../MintAndRedeemFeeDetailItem.tsx | 4 +- .../ParachainWalletBalance.tsx | 4 +- .../stakeAndUnstake/SelectTokenModal.tsx | 4 +- .../stakeAndUnstake/TokenChip.tsx | 12 +- .../UnstakeRequestsTable.tsx | 2 +- .../useLstUnlockRequestTableRows.ts | 4 +- ...uidStakingEvm.ts => liquidStakingErc20.ts} | 62 ++++----- .../liquidStaking/liquidStakingParachain.ts | 131 +++++++----------- .../constants/liquidStaking/types.ts | 123 ++++++++++++++++ .../data/liquidStaking/useExchangeRate.ts | 2 +- .../liquidStaking/useLiquidStakingItems.ts | 56 ++++---- .../liquidStaking/useLiquidStakingStore.ts | 6 +- .../data/liquidStaking/useLstRebondTx.ts | 2 +- .../liquidStaking/useLstUnlockRequests.ts | 2 +- .../liquidStaking/useLstWithdrawRedeemTx.ts | 2 +- .../data/liquidStaking/useMintTx.ts | 2 +- .../data/liquidStaking/useOngoingTimeUnits.ts | 2 +- .../liquidStaking/useParachainBalances.ts | 2 +- .../data/liquidStaking/useRedeemTx.ts | 2 +- .../data/liquifier/useLiquifierDeposit.ts | 10 +- .../liquifier/useLiquifierErc20Balance.ts | 6 +- .../data/liquifier/useLiquifierTgBalance.ts | 6 +- apps/tangle-dapp/types/liquidStaking.ts | 4 +- .../utils/liquidStaking/isLsParachainToken.ts | 10 +- 33 files changed, 356 insertions(+), 292 deletions(-) rename apps/tangle-dapp/constants/liquidStaking/{liquidStakingEvm.ts => liquidStakingErc20.ts} (66%) create mode 100644 apps/tangle-dapp/constants/liquidStaking/types.ts diff --git a/apps/tangle-dapp/app/liquid-staking/[tokenSymbol]/page.tsx b/apps/tangle-dapp/app/liquid-staking/[tokenSymbol]/page.tsx index e2057c51a..6ac078117 100644 --- a/apps/tangle-dapp/app/liquid-staking/[tokenSymbol]/page.tsx +++ b/apps/tangle-dapp/app/liquid-staking/[tokenSymbol]/page.tsx @@ -7,7 +7,7 @@ import { LiquidStakingSelectionTable } from '../../../components/LiquidStaking/L import LiquidStakeCard from '../../../components/LiquidStaking/stakeAndUnstake/LiquidStakeCard'; import LiquidUnstakeCard from '../../../components/LiquidStaking/stakeAndUnstake/LiquidUnstakeCard'; import UnstakeRequestsTable from '../../../components/LiquidStaking/unstakeRequestsTable/UnstakeRequestsTable'; -import { LsSearchParamKey } from '../../../constants/liquidStaking/liquidStakingParachain'; +import { LsSearchParamKey } from '../../../constants/liquidStaking/types'; import useSearchParamState from '../../../hooks/useSearchParamState'; import isLsParachainToken from '../../../utils/liquidStaking/isLsParachainToken'; import TabListItem from '../../restake/TabListItem'; diff --git a/apps/tangle-dapp/app/liquid-staking/page.tsx b/apps/tangle-dapp/app/liquid-staking/page.tsx index 055746510..83ac13254 100644 --- a/apps/tangle-dapp/app/liquid-staking/page.tsx +++ b/apps/tangle-dapp/app/liquid-staking/page.tsx @@ -4,7 +4,7 @@ import { FC } from 'react'; import { GlassCard } from '../../components'; import LiquidStakingTokenItem from '../../components/LiquidStaking/stakeAndUnstake/LiquidStakingTokenItem'; import StatItem from '../../components/LiquidStaking/StatItem'; -import { LIQUID_STAKING_CHAINS } from '../../constants/liquidStaking/liquidStakingParachain'; +import { LS_PROTOCOLS } from '../../constants/liquidStaking/types'; const LiquidStakingPage: FC = () => { return ( @@ -34,13 +34,13 @@ const LiquidStakingPage: FC = () => {
        - {LIQUID_STAKING_CHAINS.map((chain) => { + {LS_PROTOCOLS.map((protocol) => { return ( JSX.Element> = { diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/ChainLogo.tsx b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/ChainLogo.tsx index e08e490ce..c98aa3445 100644 --- a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/ChainLogo.tsx +++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/ChainLogo.tsx @@ -4,13 +4,13 @@ import { twMerge } from 'tailwind-merge'; import { LS_CHAIN_MAP, - LsParachainChainId, -} from '../../../constants/liquidStaking/liquidStakingParachain'; + LsProtocolId, +} from '../../../constants/liquidStaking/liquidStakingTypes'; export type ChainLogoSize = 'sm' | 'md'; export type ChainLogoProps = { - chainId?: LsParachainChainId; + chainId?: LsProtocolId; size: ChainLogoSize; isRounded?: boolean; isLiquidVariant?: boolean; @@ -34,21 +34,21 @@ const getSizeClass = (size: ChainLogoSize) => { } }; -const getBackgroundColor = (chain: LsParachainChainId) => { +const getBackgroundColor = (chain: LsProtocolId) => { switch (chain) { - case LsParachainChainId.MANTA: + case LsProtocolId.MANTA: return 'bg-[#13101D] dark:bg-[#13101D]'; - case LsParachainChainId.MOONBEAM: + case LsProtocolId.MOONBEAM: return 'bg-[#1d1336] dark:bg-[#1d1336]'; - case LsParachainChainId.PHALA: + case LsProtocolId.PHALA: return 'bg-black dark:bg-black'; - case LsParachainChainId.POLKADOT: + case LsProtocolId.POLKADOT: return 'bg-mono-0 dark:bg-mono-0'; - case LsParachainChainId.TANGLE_RESTAKING_PARACHAIN: + case LsProtocolId.TANGLE_RESTAKING_PARACHAIN: // Fix the icon SVG getting cut off on the sides by adding // a matching background. return 'bg-[#f6f4ff]'; - case LsParachainChainId.ASTAR: + case LsProtocolId.ASTAR: // No background for Astar, since it looks better without // a background. return ''; diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/ExchangeRateDetailItem.tsx b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/ExchangeRateDetailItem.tsx index 53f3fd529..0f2059fc1 100644 --- a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/ExchangeRateDetailItem.tsx +++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/ExchangeRateDetailItem.tsx @@ -1,11 +1,8 @@ import { SkeletonLoader } from '@webb-tools/webb-ui-components'; import { FC } from 'react'; -import { - LsParachainToken, - LST_PREFIX, - ParachainCurrency, -} from '../../../constants/liquidStaking/liquidStakingParachain'; +import { ParachainCurrency } from '../../../constants/liquidStaking/liquidStakingParachain'; +import { LST_PREFIX, LsToken } from '../../../constants/liquidStaking/types'; import useExchangeRate, { ExchangeRateType, } from '../../../data/liquidStaking/useExchangeRate'; @@ -13,7 +10,7 @@ import DetailItem from './DetailItem'; export type ExchangeRateDetailItemProps = { type: ExchangeRateType; - token: LsParachainToken; + token: LsToken; currency: ParachainCurrency; }; diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakeCard.tsx b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakeCard.tsx index e5f432835..53a0582b1 100644 --- a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakeCard.tsx +++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakeCard.tsx @@ -18,11 +18,11 @@ import React, { FC, useCallback, useMemo } from 'react'; import { z } from 'zod'; import { - LS_CHAIN_MAP, - LsParachainChainId, + getLsProtocolDef, + LsProtocolId, LsSearchParamKey, LST_PREFIX, -} from '../../../constants/liquidStaking/liquidStakingParachain'; +} from '../../../constants/liquidStaking/types'; import useExchangeRate, { ExchangeRateType, } from '../../../data/liquidStaking/useExchangeRate'; @@ -53,28 +53,28 @@ const LiquidStakeCard: FC = () => { const { execute: executeMintTx, status: mintTxStatus } = useMintTx(); const { nativeBalances } = useParachainBalances(); - const selectedChain = LS_CHAIN_MAP[selectedChainId]; + const selectedProtocol = getLsProtocolDef(selectedChainId); useSearchParamSync({ key: LsSearchParamKey.CHAIN_ID, value: selectedChainId, - parse: (value) => z.nativeEnum(LsParachainChainId).parse(parseInt(value)), + parse: (value) => z.nativeEnum(LsProtocolId).parse(parseInt(value)), stringify: (value) => value.toString(), setValue: setSelectedChainId, }); const exchangeRate = useExchangeRate( ExchangeRateType.NativeToLiquid, - selectedChain.currency, + selectedProtocol.currency, ); const { result: minimumMintingAmount } = useApiRx( useCallback( (api) => api.query.lstMinting.minimumMint({ - Native: selectedChain.currency, + Native: selectedProtocol.currency, }), - [selectedChain.currency], + [selectedProtocol.currency], ), TANGLE_RESTAKING_PARACHAIN_LOCAL_DEV_NETWORK.wsRpcEndpoint, ); @@ -97,8 +97,8 @@ const LiquidStakeCard: FC = () => { return null; } - return nativeBalances.get(selectedChain.token) ?? BN_ZERO; - }, [nativeBalances, selectedChain.token]); + return nativeBalances.get(selectedProtocol.token) ?? BN_ZERO; + }, [nativeBalances, selectedProtocol.token]); const handleStakeClick = useCallback(() => { if (executeMintTx === null || fromAmount === null) { @@ -107,9 +107,9 @@ const LiquidStakeCard: FC = () => { executeMintTx({ amount: fromAmount, - currency: selectedChain.currency, + currency: selectedProtocol.currency, }); - }, [executeMintTx, fromAmount, selectedChain.currency]); + }, [executeMintTx, fromAmount, selectedProtocol.currency]); const toAmount = useMemo(() => { if (fromAmount === null || exchangeRate === null) { @@ -121,8 +121,8 @@ const LiquidStakeCard: FC = () => { const walletBalance = ( setFromAmount(maximumInputAmount)} /> @@ -133,11 +133,11 @@ const LiquidStakeCard: FC = () => { { } /> {/* Details */}
        - +
        @@ -182,9 +182,9 @@ const ChainSelector: FC = ({
          - {Object.values(LsParachainChainId) + {Object.values(LsProtocolId) .filter( - (chainId): chainId is LsParachainChainId => + (chainId): chainId is LsProtocolId => chainId !== selectedChainId && typeof chainId !== 'string', ) .map((chainId) => { @@ -195,7 +195,7 @@ const ChainSelector: FC = ({ onSelect={() => setChainId(chainId)} className="px-3 normal-case" > - {LS_CHAIN_MAP[chainId].networkName} + {getLsProtocolDef(chainId).networkName} ); diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakingTokenItem.tsx b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakingTokenItem.tsx index cd074256a..273617326 100644 --- a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakingTokenItem.tsx +++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakingTokenItem.tsx @@ -8,20 +8,20 @@ import { FC, useMemo } from 'react'; import { StaticAssetPath } from '../../../constants'; import { - LsParachainToken, + LsProtocolId, LST_PREFIX, - LsParachainChainId, + LsToken, TVS_TOOLTIP, -} from '../../../constants/liquidStaking/liquidStakingParachain'; +} from '../../../constants/liquidStaking/types'; import { PagePath } from '../../../types'; import formatTangleBalance from '../../../utils/formatTangleBalance'; import StatItem from '../StatItem'; import ChainLogo from './ChainLogo'; export type LiquidStakingTokenItemProps = { - chainId: LsParachainChainId; + chainId: LsProtocolId; title: string; - tokenSymbol: LsParachainToken; + tokenSymbol: LsToken; totalValueStaked: number; totalStaked: string; }; diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidUnstakeCard.tsx b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidUnstakeCard.tsx index d3d0e26eb..d87b64986 100644 --- a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidUnstakeCard.tsx +++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidUnstakeCard.tsx @@ -12,11 +12,11 @@ import { FC, useCallback, useEffect, useMemo, useState } from 'react'; import { z } from 'zod'; import { - LS_CHAIN_MAP, - LsParachainChainId, + getLsProtocolDef, + LsProtocolId, LsSearchParamKey, LST_PREFIX, -} from '../../../constants/liquidStaking/liquidStakingParachain'; +} from '../../../constants/liquidStaking/types'; import useDelegationsOccupiedStatus from '../../../data/liquidStaking/useDelegationsOccupiedStatus'; import useExchangeRate, { ExchangeRateType, @@ -44,11 +44,10 @@ const LiquidUnstakeCard: FC = () => { useState(false); const [selectedChainId, setSelectedChainId] = - useSearchParamState({ + useSearchParamState({ key: LsSearchParamKey.CHAIN_ID, - defaultValue: LsParachainChainId.TANGLE_RESTAKING_PARACHAIN, - parser: (value) => - z.nativeEnum(LsParachainChainId).parse(parseInt(value)), + defaultValue: LsProtocolId.TANGLE_RESTAKING_PARACHAIN, + parser: (value) => z.nativeEnum(LsProtocolId).parse(parseInt(value)), stringify: (value) => value.toString(), }); @@ -60,15 +59,15 @@ const LiquidUnstakeCard: FC = () => { const { liquidBalances } = useParachainBalances(); - const selectedChain = LS_CHAIN_MAP[selectedChainId]; + const selectedProtocol = getLsProtocolDef(selectedChainId); const exchangeRate = useExchangeRate( ExchangeRateType.LiquidToNative, - selectedChain.currency, + selectedProtocol.currency, ); const { result: areAllDelegationsOccupiedOpt } = useDelegationsOccupiedStatus( - selectedChain.currency, + selectedProtocol.currency, ); useSearchParamSync({ @@ -88,9 +87,9 @@ const LiquidUnstakeCard: FC = () => { useCallback( (api) => api.query.lstMinting.minimumRedeem({ - Native: selectedChain.currency, + Native: selectedProtocol.currency, }), - [selectedChain.currency], + [selectedProtocol.currency], ), TANGLE_RESTAKING_PARACHAIN_LOCAL_DEV_NETWORK.wsRpcEndpoint, ); @@ -113,8 +112,8 @@ const LiquidUnstakeCard: FC = () => { return null; } - return liquidBalances.get(selectedChain.token) ?? BN_ZERO; - }, [liquidBalances, selectedChain.token]); + return liquidBalances.get(selectedProtocol.token) ?? BN_ZERO; + }, [liquidBalances, selectedProtocol.token]); const handleUnstakeClick = useCallback(() => { if (executeRedeemTx === null || fromAmount === null) { @@ -123,9 +122,9 @@ const LiquidUnstakeCard: FC = () => { executeRedeemTx({ amount: fromAmount, - currency: selectedChain.currency, + currency: selectedProtocol.currency, }); - }, [executeRedeemTx, fromAmount, selectedChain.currency]); + }, [executeRedeemTx, fromAmount, selectedProtocol.currency]); const toAmount = useMemo(() => { if (fromAmount === null || exchangeRate === null) { @@ -155,8 +154,8 @@ const LiquidUnstakeCard: FC = () => { const stakedWalletBalance = ( setFromAmount(maximumInputAmount)} /> @@ -167,12 +166,12 @@ const LiquidUnstakeCard: FC = () => { {/* TODO: Have a way to trigger a refresh of the amount once the wallet balance (max) button is clicked. Need to signal to the liquid staking input to update its display amount based on the `fromAmount` prop. */} { id="liquid-staking-unstake-to" chainId={selectedChainId} amount={toAmount} - decimals={selectedChain.decimals} - placeholder={`0 ${selectedChain.token}`} - token={selectedChain.token} + decimals={selectedProtocol.decimals} + placeholder={`0 ${selectedProtocol.token}`} + token={selectedProtocol.token} setChainId={setSelectedChainId} isReadOnly /> @@ -197,18 +196,18 @@ const LiquidUnstakeCard: FC = () => { {/* Details */}
          - +
          {areAllDelegationsOccupied?.isTrue && ( diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/MintAndRedeemFeeDetailItem.tsx b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/MintAndRedeemFeeDetailItem.tsx index 0ccd0d43c..6e1521317 100644 --- a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/MintAndRedeemFeeDetailItem.tsx +++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/MintAndRedeemFeeDetailItem.tsx @@ -4,7 +4,7 @@ import { SkeletonLoader } from '@webb-tools/webb-ui-components'; import { FC, useMemo } from 'react'; import { EMPTY_VALUE_PLACEHOLDER } from '../../../constants'; -import { LsParachainToken } from '../../../constants/liquidStaking/liquidStakingParachain'; +import { LsToken } from '../../../constants/liquidStaking/types'; import useMintAndRedeemFees from '../../../data/liquidStaking/useMintAndRedeemFees'; import formatBn from '../../../utils/formatBn'; import scaleAmountByPermill from '../../../utils/scaleAmountByPermill'; @@ -13,7 +13,7 @@ import DetailItem from './DetailItem'; export type MintAndRedeemFeeDetailItemProps = { isMinting: boolean; intendedAmount: BN | null; - token: LsParachainToken; + token: LsToken; }; const MintAndRedeemFeeDetailItem: FC = ({ diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/ParachainWalletBalance.tsx b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/ParachainWalletBalance.tsx index 2a9aab2d3..78bc76b26 100644 --- a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/ParachainWalletBalance.tsx +++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/ParachainWalletBalance.tsx @@ -13,14 +13,14 @@ import { FC, useCallback, useMemo, useState } from 'react'; import { twMerge } from 'tailwind-merge'; import { EMPTY_VALUE_PLACEHOLDER } from '../../../constants'; -import { LsParachainToken } from '../../../constants/liquidStaking/liquidStakingParachain'; +import { LsToken } from '../../../constants/liquidStaking/types'; import useParachainBalances from '../../../data/liquidStaking/useParachainBalances'; import useSubstrateAddress from '../../../hooks/useSubstrateAddress'; import formatBn from '../../../utils/formatBn'; export type ParachainWalletBalanceProps = { isNative?: boolean; - token: LsParachainToken; + token: LsToken; decimals: number; tooltip?: string; onlyShowTooltipWhenBalanceIsSet?: boolean; diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/SelectTokenModal.tsx b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/SelectTokenModal.tsx index a04366c20..1d628e8a5 100644 --- a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/SelectTokenModal.tsx +++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/SelectTokenModal.tsx @@ -8,7 +8,7 @@ import { } from '@webb-tools/webb-ui-components'; import { FC, useEffect, useMemo } from 'react'; -import { LsParachainChainId } from '../../../constants/liquidStaking/liquidStakingParachain'; +import { LsProtocolId } from '../../../constants/liquidStaking/types'; import { AnySubstrateAddress } from '../../../types/utils'; import formatBn from '../../../utils/formatBn'; import AddressLink from '../AddressLink'; @@ -106,7 +106,7 @@ const TokenListItem: FC = ({ > {/* Information */}
          - +
          diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/TokenChip.tsx b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/TokenChip.tsx index a665b7ffc..60a9cfce4 100644 --- a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/TokenChip.tsx +++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/TokenChip.tsx @@ -4,15 +4,15 @@ import { FC } from 'react'; import { twMerge } from 'tailwind-merge'; import { - LIQUID_STAKING_CHAINS, - LsParachainToken, + LS_PROTOCOLS, LST_PREFIX, -} from '../../../constants/liquidStaking/liquidStakingParachain'; + LsToken, +} from '../../../constants/liquidStaking/types'; import ChainLogo from './ChainLogo'; import DropdownChevronIcon from './DropdownChevronIcon'; type TokenChipProps = { - token?: LsParachainToken; + token?: LsToken; isLiquidVariant: boolean; onClick?: () => void; }; @@ -23,11 +23,11 @@ const TokenChip: FC = ({ token, isLiquidVariant, onClick }) => { return null; } - const result = LIQUID_STAKING_CHAINS.find((chain) => chain.token === token); + const result = LS_PROTOCOLS.find((protocol) => protocol.token === token); assert( result !== undefined, - 'All tokens should have a corresponding chain', + 'All tokens should have a corresponding protocol', ); return result; diff --git a/apps/tangle-dapp/components/LiquidStaking/unstakeRequestsTable/UnstakeRequestsTable.tsx b/apps/tangle-dapp/components/LiquidStaking/unstakeRequestsTable/UnstakeRequestsTable.tsx index 17886ee75..1f0a9d482 100644 --- a/apps/tangle-dapp/components/LiquidStaking/unstakeRequestsTable/UnstakeRequestsTable.tsx +++ b/apps/tangle-dapp/components/LiquidStaking/unstakeRequestsTable/UnstakeRequestsTable.tsx @@ -28,8 +28,8 @@ import React from 'react'; import { twMerge } from 'tailwind-merge'; import { - ParachainCurrency, LsSimpleParachainTimeUnit, + ParachainCurrency, } from '../../../constants/liquidStaking/liquidStakingParachain'; import useSubstrateAddress from '../../../hooks/useSubstrateAddress'; import { AnySubstrateAddress } from '../../../types/utils'; diff --git a/apps/tangle-dapp/components/LiquidStaking/unstakeRequestsTable/useLstUnlockRequestTableRows.ts b/apps/tangle-dapp/components/LiquidStaking/unstakeRequestsTable/useLstUnlockRequestTableRows.ts index 5348de38f..609aa894c 100644 --- a/apps/tangle-dapp/components/LiquidStaking/unstakeRequestsTable/useLstUnlockRequestTableRows.ts +++ b/apps/tangle-dapp/components/LiquidStaking/unstakeRequestsTable/useLstUnlockRequestTableRows.ts @@ -2,7 +2,7 @@ import assert from 'assert'; import { useMemo } from 'react'; import { - LIQUID_STAKING_CHAINS, + LS_PARACHAIN_CHAIN_MAP, LsSimpleParachainTimeUnit, } from '../../../constants/liquidStaking/liquidStakingParachain'; import useLstUnlockRequests from '../../../data/liquidStaking/useLstUnlockRequests'; @@ -28,7 +28,7 @@ const useLstUnlockRequestTableRows = () => { }) .map((request) => { // Find the corresponding chain in order to get the decimals. - const chain = LIQUID_STAKING_CHAINS.find( + const chain = Object.values(LS_PARACHAIN_CHAIN_MAP).find( (chain) => chain.currency === request.currency, ); diff --git a/apps/tangle-dapp/constants/liquidStaking/liquidStakingEvm.ts b/apps/tangle-dapp/constants/liquidStaking/liquidStakingErc20.ts similarity index 66% rename from apps/tangle-dapp/constants/liquidStaking/liquidStakingEvm.ts rename to apps/tangle-dapp/constants/liquidStaking/liquidStakingErc20.ts index 24bc9e39b..68ed777e8 100644 --- a/apps/tangle-dapp/constants/liquidStaking/liquidStakingEvm.ts +++ b/apps/tangle-dapp/constants/liquidStaking/liquidStakingErc20.ts @@ -1,32 +1,13 @@ -import { HexString } from '@polkadot/util/types'; - -export type LsErc20TokenDef = { - id: LsErc20TokenId; - name: string; - token: LsErc20Token; - address: HexString; - liquifierAdapterAddress: HexString; - liquifierTgTokenAddress: HexString; -}; - -export enum LsErc20TokenId { - Chainlink, - TheGraph, - Livepeer, - Polygon, -} - -export enum LsErc20Token { - LINK, - GRT, - LPT, - POL, -} +import { LsErc20TokenId, LsProtocolId, LsToken } from './types'; +import { LsErc20TokenDef } from './types'; const ChainlinkErc20TokenDef: LsErc20TokenDef = { - id: LsErc20TokenId.Chainlink, + type: 'erc20', + id: LsProtocolId.CHAINLINK, name: 'Chainlink', - token: LsErc20Token.LINK, + networkName: 'Ethereum Mainnet', + token: LsToken.LINK, + decimals: 18, // TODO: Use Liquifier's testnet address if the environment is development. address: '0x514910771AF9Ca656af840dff83E8264EcF986CA', // TODO: Use the actual Chainlink Liquifier Adapter address. This is likely deployed to a testnet (Tenderly?). @@ -35,9 +16,12 @@ const ChainlinkErc20TokenDef: LsErc20TokenDef = { }; const TheGraphErc20TokenDef: LsErc20TokenDef = { - id: LsErc20TokenId.TheGraph, + type: 'erc20', + id: LsProtocolId.THE_GRAPH, name: 'The Graph', - token: LsErc20Token.GRT, + networkName: 'Ethereum Mainnet', + token: LsToken.GRT, + decimals: 18, // TODO: Use Liquifier's testnet address if the environment is development. address: '0xc944E90C64B2c07662A292be6244BDf05Cda44a7', // TODO: Use the actual Chainlink Liquifier Adapter address. This is likely deployed to a testnet (Tenderly?). @@ -46,9 +30,12 @@ const TheGraphErc20TokenDef: LsErc20TokenDef = { }; const LivepeerErc20TokenDef: LsErc20TokenDef = { - id: LsErc20TokenId.Livepeer, + type: 'erc20', + id: LsProtocolId.LIVEPEER, name: 'Livepeer', - token: LsErc20Token.LPT, + networkName: 'Ethereum Mainnet', + token: LsToken.LPT, + decimals: 18, // TODO: Use Liquifier's testnet address if the environment is development. address: '0x58b6A8A3302369DAEc383334672404Ee733aB239', // TODO: Use the actual Chainlink Liquifier Adapter address. This is likely deployed to a testnet (Tenderly?). @@ -57,9 +44,12 @@ const LivepeerErc20TokenDef: LsErc20TokenDef = { }; const PolygonErc20TokenDef: LsErc20TokenDef = { - id: LsErc20TokenId.Polygon, + type: 'erc20', + id: LsProtocolId.POLYGON, name: 'Polygon', - token: LsErc20Token.POL, + networkName: 'Ethereum Mainnet', + token: LsToken.POL, + decimals: 18, // TODO: Use Liquifier's testnet address if the environment is development. address: '0x0D500B1d8E8eF31E21C99d1Db9A6444d3ADf1270', // TODO: Use the actual Chainlink Liquifier Adapter address. This is likely deployed to a testnet (Tenderly?). @@ -68,8 +58,8 @@ const PolygonErc20TokenDef: LsErc20TokenDef = { }; export const LS_ERC20_TOKEN_MAP: Record = { - [LsErc20TokenId.Chainlink]: ChainlinkErc20TokenDef, - [LsErc20TokenId.TheGraph]: TheGraphErc20TokenDef, - [LsErc20TokenId.Livepeer]: LivepeerErc20TokenDef, - [LsErc20TokenId.Polygon]: PolygonErc20TokenDef, + [LsProtocolId.CHAINLINK]: ChainlinkErc20TokenDef, + [LsProtocolId.THE_GRAPH]: TheGraphErc20TokenDef, + [LsProtocolId.LIVEPEER]: LivepeerErc20TokenDef, + [LsProtocolId.POLYGON]: PolygonErc20TokenDef, }; diff --git a/apps/tangle-dapp/constants/liquidStaking/liquidStakingParachain.ts b/apps/tangle-dapp/constants/liquidStaking/liquidStakingParachain.ts index 844ef4ad5..7e3fee9df 100644 --- a/apps/tangle-dapp/constants/liquidStaking/liquidStakingParachain.ts +++ b/apps/tangle-dapp/constants/liquidStaking/liquidStakingParachain.ts @@ -2,50 +2,22 @@ import { TanglePrimitivesCurrencyTokenSymbol, TanglePrimitivesTimeUnit, } from '@polkadot/types/lookup'; -import { BN } from '@polkadot/util'; import { TANGLE_TOKEN_DECIMALS } from '@webb-tools/dapp-config'; import { TANGLE_RESTAKING_PARACHAIN_LOCAL_DEV_NETWORK } from '@webb-tools/webb-ui-components/constants/networks'; import { StaticAssetPath } from '..'; - -export enum LsParachainChainId { - POLKADOT, - PHALA, - MOONBEAM, - ASTAR, - MANTA, - TANGLE_RESTAKING_PARACHAIN, -} - -export enum LsParachainToken { - DOT = 'DOT', - GLMR = 'GLMR', - MANTA = 'MANTA', - ASTAR = 'ASTR', - PHALA = 'PHALA', - TNT = 'TNT', -} - -// TODO: Temporary manual override until the Parachain types are updated. -export type ParachainCurrency = TanglePrimitivesCurrencyTokenSymbol['type']; -// | Exclude -// | 'Tnt'; - -export type LsParachainProtocolDef = { - id: LsParachainChainId; - name: string; - token: LsParachainToken; - logo: StaticAssetPath; - networkName: string; - currency: ParachainCurrency; - decimals: number; - rpcEndpoint: string; -}; - -const POLKADOT: LsParachainProtocolDef = { - id: LsParachainChainId.POLKADOT, +import { + LsParachainChainDef, + LsParachainChainId, + LsProtocolId, + LsToken, +} from './types'; + +const POLKADOT: LsParachainChainDef = { + type: 'parachain', + id: LsProtocolId.POLKADOT, name: 'Polkadot', - token: LsParachainToken.DOT, + token: LsToken.DOT, logo: StaticAssetPath.LIQUID_STAKING_TOKEN_POLKADOT, networkName: 'Polkadot Mainnet', currency: 'Dot', @@ -53,10 +25,11 @@ const POLKADOT: LsParachainProtocolDef = { rpcEndpoint: 'wss://polkadot-rpc.dwellir.com', }; -const PHALA: LsParachainProtocolDef = { - id: LsParachainChainId.PHALA, +const PHALA: LsParachainChainDef = { + type: 'parachain', + id: LsProtocolId.PHALA, name: 'Phala', - token: LsParachainToken.PHALA, + token: LsToken.PHALA, logo: StaticAssetPath.LIQUID_STAKING_TOKEN_PHALA, networkName: 'Phala', currency: 'Pha', @@ -64,10 +37,11 @@ const PHALA: LsParachainProtocolDef = { rpcEndpoint: 'wss://api.phala.network/ws', }; -const MOONBEAM: LsParachainProtocolDef = { - id: LsParachainChainId.MOONBEAM, +const MOONBEAM: LsParachainChainDef = { + type: 'parachain', + id: LsProtocolId.MOONBEAM, name: 'Moonbeam', - token: LsParachainToken.GLMR, + token: LsToken.GLMR, logo: StaticAssetPath.LIQUID_STAKING_TOKEN_GLIMMER, networkName: 'Moonbeam', // TODO: No currency entry for GLMR in the Tangle Primitives? @@ -76,10 +50,11 @@ const MOONBEAM: LsParachainProtocolDef = { rpcEndpoint: 'wss://moonbeam.api.onfinality.io/public-ws', }; -const ASTAR: LsParachainProtocolDef = { - id: LsParachainChainId.ASTAR, +const ASTAR: LsParachainChainDef = { + type: 'parachain', + id: LsProtocolId.ASTAR, name: 'Astar', - token: LsParachainToken.ASTAR, + token: LsToken.ASTAR, logo: StaticAssetPath.LIQUID_STAKING_TOKEN_ASTAR, networkName: 'Astar', // TODO: No currency entry for ASTAR in the Tangle Primitives? @@ -88,10 +63,11 @@ const ASTAR: LsParachainProtocolDef = { rpcEndpoint: 'wss://astar.api.onfinality.io/public-ws', }; -const MANTA: LsParachainProtocolDef = { - id: LsParachainChainId.MANTA, +const MANTA: LsParachainChainDef = { + type: 'parachain', + id: LsProtocolId.MANTA, name: 'Manta', - token: LsParachainToken.MANTA, + token: LsToken.MANTA, logo: StaticAssetPath.LIQUID_STAKING_TOKEN_MANTA, networkName: 'Manta', // TODO: No currency entry for ASTAR in the Tangle Primitives? @@ -100,10 +76,11 @@ const MANTA: LsParachainProtocolDef = { rpcEndpoint: 'wss://ws.manta.systems', }; -const TANGLE_RESTAKING_PARACHAIN: LsParachainProtocolDef = { - id: LsParachainChainId.TANGLE_RESTAKING_PARACHAIN, +const TANGLE_RESTAKING_PARACHAIN: LsParachainChainDef = { + type: 'parachain', + id: LsProtocolId.TANGLE_RESTAKING_PARACHAIN, name: 'Tangle Parachain', - token: LsParachainToken.TNT, + token: LsToken.TNT, logo: StaticAssetPath.LIQUID_STAKING_TANGLE_LOGO, networkName: 'Tangle Parachain', currency: 'Bnc', @@ -111,29 +88,23 @@ const TANGLE_RESTAKING_PARACHAIN: LsParachainProtocolDef = { rpcEndpoint: TANGLE_RESTAKING_PARACHAIN_LOCAL_DEV_NETWORK.wsRpcEndpoint, }; -export const LS_CHAIN_MAP: Record = - { - [LsParachainChainId.POLKADOT]: POLKADOT, - [LsParachainChainId.PHALA]: PHALA, - [LsParachainChainId.MOONBEAM]: MOONBEAM, - [LsParachainChainId.ASTAR]: ASTAR, - [LsParachainChainId.MANTA]: MANTA, - [LsParachainChainId.TANGLE_RESTAKING_PARACHAIN]: TANGLE_RESTAKING_PARACHAIN, - }; - -export const LIQUID_STAKING_CHAINS: LsParachainProtocolDef[] = - Object.values(LS_CHAIN_MAP); - -export const TVS_TOOLTIP = - "Total Value Staked (TVS) refers to the total value of assets that are currently staked for this network in fiat currency. Generally used as an indicator of a network's security and trustworthiness."; - -export const LST_PREFIX = 'tg'; +export const LS_PARACHAIN_CHAIN_MAP: Record< + LsParachainChainId, + LsParachainChainDef +> = { + [LsProtocolId.POLKADOT]: POLKADOT, + [LsProtocolId.PHALA]: PHALA, + [LsProtocolId.MOONBEAM]: MOONBEAM, + [LsProtocolId.ASTAR]: ASTAR, + [LsProtocolId.MANTA]: MANTA, + [LsProtocolId.TANGLE_RESTAKING_PARACHAIN]: TANGLE_RESTAKING_PARACHAIN, +}; // TODO: These should be moved/managed in libs/webb-ui-components/src/constants/networks.ts and not here. This is just a temporary solution. export type Network = { name: string; endpoint: string; - tokenSymbol: LsParachainToken; + tokenSymbol: LsToken; chainType: NetworkType; }; @@ -142,6 +113,11 @@ export enum NetworkType { PARACHAIN = 'Parachain', } +// TODO: Temporary manual override until the Parachain types are updated. +export type ParachainCurrency = TanglePrimitivesCurrencyTokenSymbol['type']; +// | Exclude +// | 'Tnt'; + export type LsParachainCurrencyKey = | { lst: ParachainCurrency } | { Native: ParachainCurrency }; @@ -152,14 +128,3 @@ export type LsSimpleParachainTimeUnit = { value: number; unit: LsParachainTimeUnit; }; - -export type LsCardSearchParams = { - amount: BN; - chainId: LsParachainChainId; -}; - -export enum LsSearchParamKey { - AMOUNT = 'amount', - CHAIN_ID = 'chainId', - ACTION = 'action', -} diff --git a/apps/tangle-dapp/constants/liquidStaking/types.ts b/apps/tangle-dapp/constants/liquidStaking/types.ts new file mode 100644 index 000000000..6cd16edc4 --- /dev/null +++ b/apps/tangle-dapp/constants/liquidStaking/types.ts @@ -0,0 +1,123 @@ +import { BN } from '@polkadot/util'; +import { HexString } from '@polkadot/util/types'; +import assert from 'assert'; + +import { StaticAssetPath } from '..'; +import { LS_ERC20_TOKEN_MAP } from './liquidStakingErc20'; +import { + LS_PARACHAIN_CHAIN_MAP, + ParachainCurrency, +} from './liquidStakingParachain'; + +export enum LsProtocolId { + POLKADOT, + PHALA, + MOONBEAM, + ASTAR, + MANTA, + TANGLE_RESTAKING_PARACHAIN, + CHAINLINK, + THE_GRAPH, + LIVEPEER, + POLYGON, +} + +export type LsErc20TokenId = + | LsProtocolId.CHAINLINK + | LsProtocolId.THE_GRAPH + | LsProtocolId.LIVEPEER + | LsProtocolId.POLYGON; + +export type LsParachainChainId = Exclude; + +export enum LsToken { + DOT = 'DOT', + GLMR = 'GLMR', + MANTA = 'MANTA', + ASTAR = 'ASTR', + PHALA = 'PHALA', + TNT = 'TNT', + LINK = 'LINK', + GRT = 'GRT', + LPT = 'LPT', + POL = 'POL', +} + +export type LsErc20Token = + | LsToken.LINK + | LsToken.GRT + | LsToken.LPT + | LsToken.POL; + +export type LsParachainToken = Exclude; + +export const LS_ERC20_TOKEN_IDS = [ + LsProtocolId.CHAINLINK, + LsProtocolId.THE_GRAPH, + LsProtocolId.LIVEPEER, + LsProtocolId.POLYGON, +] as const satisfies LsErc20TokenId[]; + +export type LsParachainChainDef = { + type: 'parachain'; + id: LsProtocolId; + name: string; + token: LsParachainToken; + logo: StaticAssetPath; + networkName: string; + currency: ParachainCurrency; + decimals: number; + rpcEndpoint: string; +}; + +export type LsErc20TokenDef = { + type: 'erc20'; + id: LsErc20TokenId; + name: string; + networkName: string; + decimals: number; + token: LsErc20Token; + address: HexString; + liquifierAdapterAddress: HexString; + liquifierTgTokenAddress: HexString; +}; + +export type LsProtocolDef = LsParachainChainDef | LsErc20TokenDef; + +export type LsCardSearchParams = { + amount: BN; + chainId: LsProtocolId; +}; + +export enum LsSearchParamKey { + AMOUNT = 'amount', + CHAIN_ID = 'chainId', + ACTION = 'action', +} + +export const LS_PROTOCOLS: LsProtocolDef[] = [ + ...Object.values(LS_PARACHAIN_CHAIN_MAP), + ...Object.values(LS_ERC20_TOKEN_MAP), +]; + +type IdToDefMap = T extends LsParachainChainId + ? LsParachainChainDef + : LsErc20TokenDef; + +export const getLsProtocolDef = ( + id: T, +): IdToDefMap => { + const result = LS_PROTOCOLS.find((def) => def.id === id); + + assert( + result !== undefined, + `No protocol definition found for id: ${id} (did you forget to add a new entry to the list?)`, + ); + + return result as IdToDefMap; +}; + +export const TVS_TOOLTIP = + "Total Value Staked (TVS) refers to the total value of assets that are currently staked for this network in fiat currency. Generally used as an indicator of a network's security and trustworthiness."; + +export const LST_PREFIX = 'tg'; diff --git a/apps/tangle-dapp/data/liquidStaking/useExchangeRate.ts b/apps/tangle-dapp/data/liquidStaking/useExchangeRate.ts index fa2960772..9aa1b6b52 100644 --- a/apps/tangle-dapp/data/liquidStaking/useExchangeRate.ts +++ b/apps/tangle-dapp/data/liquidStaking/useExchangeRate.ts @@ -2,8 +2,8 @@ import { TANGLE_RESTAKING_PARACHAIN_LOCAL_DEV_NETWORK } from '@webb-tools/webb-u import { useMemo } from 'react'; import { - ParachainCurrency, LsParachainCurrencyKey, + ParachainCurrency, } from '../../constants/liquidStaking/liquidStakingParachain'; import useApiRx from '../../hooks/useApiRx'; import calculateBnRatio from '../../utils/calculateBnRatio'; diff --git a/apps/tangle-dapp/data/liquidStaking/useLiquidStakingItems.ts b/apps/tangle-dapp/data/liquidStaking/useLiquidStakingItems.ts index 6586c11af..fa5c631c5 100644 --- a/apps/tangle-dapp/data/liquidStaking/useLiquidStakingItems.ts +++ b/apps/tangle-dapp/data/liquidStaking/useLiquidStakingItems.ts @@ -2,9 +2,9 @@ import { BN_ZERO } from '@polkadot/util'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { - LS_CHAIN_MAP, - LsParachainChainId, -} from '../../constants/liquidStaking/liquidStakingParachain'; + getLsProtocolDef, + LsProtocolId, +} from '../../constants/liquidStaking/types'; import useLocalStorage, { LocalStorageKey } from '../../hooks/useLocalStorage'; import { Collator, @@ -27,7 +27,7 @@ import { fetchVaultsAndStakePools, } from './helper'; -const useLiquidStakingItems = (selectedChain: LsParachainChainId) => { +const useLiquidStakingItems = (selectedChain: LsProtocolId) => { const { setWithPreviousValue: setLiquidStakingTableData } = useLocalStorage( LocalStorageKey.LIQUID_STAKING_TABLE_DATA, ); @@ -41,8 +41,8 @@ const useLiquidStakingItems = (selectedChain: LsParachainChainId) => { const dataType = useMemo(() => getDataType(selectedChain), [selectedChain]); const fetchData = useCallback( - async (chain: LsParachainChainId) => { - const endpoint = LS_CHAIN_MAP[chain]?.rpcEndpoint; + async (protocolId: LsProtocolId) => { + const endpoint = getLsProtocolDef(protocolId)?.rpcEndpoint; if (!endpoint) { setItems([]); @@ -53,31 +53,31 @@ const useLiquidStakingItems = (selectedChain: LsParachainChainId) => { let fetchedItems: Validator[] | VaultOrStakePool[] | Dapp[] | Collator[] = []; - switch (chain) { - case LsParachainChainId.POLKADOT: + switch (protocolId) { + case LsProtocolId.POLKADOT: fetchedItems = await getValidators(endpoint); break; - case LsParachainChainId.ASTAR: + case LsProtocolId.ASTAR: fetchedItems = await getDapps(endpoint); break; - case LsParachainChainId.PHALA: + case LsProtocolId.PHALA: fetchedItems = await getVaultsAndStakePools(endpoint); break; - case LsParachainChainId.MOONBEAM: + case LsProtocolId.MOONBEAM: fetchedItems = await getCollators( endpoint, - LsParachainChainId.MOONBEAM, + LsProtocolId.MOONBEAM, 'https://stakeglmr.com/', ); break; - case LsParachainChainId.MANTA: + case LsProtocolId.MANTA: fetchedItems = await getCollators( endpoint, - LsParachainChainId.MANTA, + LsProtocolId.MANTA, 'https://manta.subscan.io/account/', ); break; @@ -90,7 +90,7 @@ const useLiquidStakingItems = (selectedChain: LsParachainChainId) => { setItems(fetchedItems); setLiquidStakingTableData((prev) => ({ ...prev?.value, - [chain]: fetchedItems, + [protocolId]: fetchedItems, })); setIsLoading(false); }, @@ -113,18 +113,18 @@ const useLiquidStakingItems = (selectedChain: LsParachainChainId) => { export default useLiquidStakingItems; -const getDataType = (chain: LsParachainChainId) => { +const getDataType = (chain: LsProtocolId) => { switch (chain) { - case LsParachainChainId.MANTA: + case LsProtocolId.MANTA: return LiquidStakingItem.COLLATOR; - case LsParachainChainId.MOONBEAM: + case LsProtocolId.MOONBEAM: return LiquidStakingItem.COLLATOR; - case LsParachainChainId.TANGLE_RESTAKING_PARACHAIN: - case LsParachainChainId.POLKADOT: + case LsProtocolId.TANGLE_RESTAKING_PARACHAIN: + case LsProtocolId.POLKADOT: return LiquidStakingItem.VALIDATOR; - case LsParachainChainId.PHALA: + case LsProtocolId.PHALA: return LiquidStakingItem.VAULT_OR_STAKE_POOL; - case LsParachainChainId.ASTAR: + case LsProtocolId.ASTAR: return LiquidStakingItem.DAPP; default: return LiquidStakingItem.VALIDATOR; @@ -160,7 +160,7 @@ const getValidators = async (endpoint: string): Promise => { totalValueStaked: totalValueStaked || BN_ZERO, validatorAPY: 0, validatorCommission: commission || BN_ZERO, - chainId: LsParachainChainId.POLKADOT, + chainId: LsProtocolId.POLKADOT, chainDecimals, chainTokenSymbol, itemType: LiquidStakingItem.VALIDATOR, @@ -207,7 +207,7 @@ const getDapps = async (endpoint: string): Promise => { dappContractType: dappInfo ? dappInfo.contractType || '' : '', commission: BN_ZERO, totalValueStaked: totalValueStaked || BN_ZERO, - chainId: LsParachainChainId.ASTAR, + chainId: LsProtocolId.ASTAR, chainDecimals, chainTokenSymbol, itemType: LiquidStakingItem.DAPP, @@ -237,7 +237,7 @@ const getVaultsAndStakePools = async ( vaultOrStakePoolName: val.id, commission: val.commission, totalValueStaked: val.totalValueStaked, - chainId: LsParachainChainId.PHALA, + chainId: LsProtocolId.PHALA, chainDecimals, chainTokenSymbol, type, @@ -249,7 +249,7 @@ const getVaultsAndStakePools = async ( const getCollators = async ( endpoint: string, - chainId: LsParachainChainId, + chainId: LsProtocolId, href: string, ): Promise => { const [ @@ -272,9 +272,9 @@ const getCollators = async ( let collatorExternalLink = ''; - if (chainId === LsParachainChainId.MOONBEAM) { + if (chainId === LsProtocolId.MOONBEAM) { collatorExternalLink = href; - } else if (chainId === LsParachainChainId.MANTA) { + } else if (chainId === LsProtocolId.MANTA) { collatorExternalLink = href + collator; } diff --git a/apps/tangle-dapp/data/liquidStaking/useLiquidStakingStore.ts b/apps/tangle-dapp/data/liquidStaking/useLiquidStakingStore.ts index 088c7ebbf..3975a01dc 100644 --- a/apps/tangle-dapp/data/liquidStaking/useLiquidStakingStore.ts +++ b/apps/tangle-dapp/data/liquidStaking/useLiquidStakingStore.ts @@ -1,9 +1,9 @@ import { create } from 'zustand'; -import { LsParachainChainId } from '../../constants/liquidStaking/liquidStakingParachain'; +import { LsProtocolId } from '../../constants/liquidStaking/types'; type State = { - selectedChainId: LsParachainChainId; + selectedChainId: LsProtocolId; selectedItems: Set; }; @@ -15,7 +15,7 @@ type Actions = { type Store = State & Actions; export const useLiquidStakingStore = create((set) => ({ - selectedChainId: LsParachainChainId.TANGLE_RESTAKING_PARACHAIN, + selectedChainId: LsProtocolId.TANGLE_RESTAKING_PARACHAIN, selectedItems: new Set(), setSelectedChainId: (selectedChainId) => set({ selectedChainId }), setSelectedItems: (selectedItems) => set({ selectedItems }), diff --git a/apps/tangle-dapp/data/liquidStaking/useLstRebondTx.ts b/apps/tangle-dapp/data/liquidStaking/useLstRebondTx.ts index 3a49bc29a..abca821ab 100644 --- a/apps/tangle-dapp/data/liquidStaking/useLstRebondTx.ts +++ b/apps/tangle-dapp/data/liquidStaking/useLstRebondTx.ts @@ -2,8 +2,8 @@ import { TANGLE_RESTAKING_PARACHAIN_LOCAL_DEV_NETWORK } from '@webb-tools/webb-u import { TxName } from '../../constants'; import { - ParachainCurrency, LsParachainCurrencyKey, + ParachainCurrency, } from '../../constants/liquidStaking/liquidStakingParachain'; import { useSubstrateTxWithNotification } from '../../hooks/useSubstrateTx'; import optimizeTxBatch from '../../utils/optimizeTxBatch'; diff --git a/apps/tangle-dapp/data/liquidStaking/useLstUnlockRequests.ts b/apps/tangle-dapp/data/liquidStaking/useLstUnlockRequests.ts index b7a48c05c..f1cdbf30c 100644 --- a/apps/tangle-dapp/data/liquidStaking/useLstUnlockRequests.ts +++ b/apps/tangle-dapp/data/liquidStaking/useLstUnlockRequests.ts @@ -12,8 +12,8 @@ import { useCallback, useMemo } from 'react'; import { map } from 'rxjs'; import { - ParachainCurrency, LsSimpleParachainTimeUnit, + ParachainCurrency, } from '../../constants/liquidStaking/liquidStakingParachain'; import useApiRx from '../../hooks/useApiRx'; import useSubstrateAddress from '../../hooks/useSubstrateAddress'; diff --git a/apps/tangle-dapp/data/liquidStaking/useLstWithdrawRedeemTx.ts b/apps/tangle-dapp/data/liquidStaking/useLstWithdrawRedeemTx.ts index 3240adf08..9ef806d63 100644 --- a/apps/tangle-dapp/data/liquidStaking/useLstWithdrawRedeemTx.ts +++ b/apps/tangle-dapp/data/liquidStaking/useLstWithdrawRedeemTx.ts @@ -2,8 +2,8 @@ import { useCallback } from 'react'; import { TxName } from '../../constants'; import { - ParachainCurrency, LsParachainCurrencyKey, + ParachainCurrency, } from '../../constants/liquidStaking/liquidStakingParachain'; import { useSubstrateTxWithNotification } from '../../hooks/useSubstrateTx'; import optimizeTxBatch from '../../utils/optimizeTxBatch'; diff --git a/apps/tangle-dapp/data/liquidStaking/useMintTx.ts b/apps/tangle-dapp/data/liquidStaking/useMintTx.ts index 7ad5d229e..f1392051a 100644 --- a/apps/tangle-dapp/data/liquidStaking/useMintTx.ts +++ b/apps/tangle-dapp/data/liquidStaking/useMintTx.ts @@ -8,8 +8,8 @@ import { TANGLE_RESTAKING_PARACHAIN_LOCAL_DEV_NETWORK } from '@webb-tools/webb-u import { TxName } from '../../constants'; import { - ParachainCurrency, LsParachainCurrencyKey, + ParachainCurrency, } from '../../constants/liquidStaking/liquidStakingParachain'; import { useSubstrateTxWithNotification } from '../../hooks/useSubstrateTx'; diff --git a/apps/tangle-dapp/data/liquidStaking/useOngoingTimeUnits.ts b/apps/tangle-dapp/data/liquidStaking/useOngoingTimeUnits.ts index 232be501c..f1a360426 100644 --- a/apps/tangle-dapp/data/liquidStaking/useOngoingTimeUnits.ts +++ b/apps/tangle-dapp/data/liquidStaking/useOngoingTimeUnits.ts @@ -3,8 +3,8 @@ import { TANGLE_RESTAKING_PARACHAIN_LOCAL_DEV_NETWORK } from '@webb-tools/webb-u import { useCallback, useMemo } from 'react'; import { - ParachainCurrency, LsSimpleParachainTimeUnit, + ParachainCurrency, } from '../../constants/liquidStaking/liquidStakingParachain'; import useApiRx from '../../hooks/useApiRx'; import tangleTimeUnitToSimpleInstance from '../../utils/liquidStaking/tangleTimeUnitToSimpleInstance'; diff --git a/apps/tangle-dapp/data/liquidStaking/useParachainBalances.ts b/apps/tangle-dapp/data/liquidStaking/useParachainBalances.ts index 05e5bd565..5c4487957 100644 --- a/apps/tangle-dapp/data/liquidStaking/useParachainBalances.ts +++ b/apps/tangle-dapp/data/liquidStaking/useParachainBalances.ts @@ -8,7 +8,7 @@ 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/liquidStakingParachain'; +import { LsParachainToken } from '../../constants/liquidStaking/types'; import useApiRx from '../../hooks/useApiRx'; import useSubstrateAddress from '../../hooks/useSubstrateAddress'; import isLsParachainToken from '../../utils/liquidStaking/isLsParachainToken'; diff --git a/apps/tangle-dapp/data/liquidStaking/useRedeemTx.ts b/apps/tangle-dapp/data/liquidStaking/useRedeemTx.ts index b9c2de799..5d365b078 100644 --- a/apps/tangle-dapp/data/liquidStaking/useRedeemTx.ts +++ b/apps/tangle-dapp/data/liquidStaking/useRedeemTx.ts @@ -7,8 +7,8 @@ import { TANGLE_RESTAKING_PARACHAIN_LOCAL_DEV_NETWORK } from '@webb-tools/webb-u import { TxName } from '../../constants'; import { - ParachainCurrency, LsParachainCurrencyKey, + ParachainCurrency, } from '../../constants/liquidStaking/liquidStakingParachain'; import { useSubstrateTxWithNotification } from '../../hooks/useSubstrateTx'; diff --git a/apps/tangle-dapp/data/liquifier/useLiquifierDeposit.ts b/apps/tangle-dapp/data/liquifier/useLiquifierDeposit.ts index 4b47934f7..da880f034 100644 --- a/apps/tangle-dapp/data/liquifier/useLiquifierDeposit.ts +++ b/apps/tangle-dapp/data/liquifier/useLiquifierDeposit.ts @@ -4,11 +4,9 @@ import { useCallback } from 'react'; import { erc20Abi } from 'viem'; import { TxName } from '../../constants'; -import { - LS_ERC20_TOKEN_MAP, - LsErc20TokenId, -} from '../../constants/liquidStaking/liquidStakingEvm'; +import { LS_ERC20_TOKEN_MAP } from '../../constants/liquidStaking/liquidStakingErc20'; import liquifierAbi from '../../constants/liquidStaking/liquifierAbi'; +import { LsErc20TokenId } from '../../constants/liquidStaking/types'; import useEvmAddress20 from '../../hooks/useEvmAddress'; import useTxNotification from '../../hooks/useTxNotification'; import useContract from './useContract'; @@ -53,7 +51,7 @@ const useLiquifierDeposit = () => { activeEvmAddress20 !== null; const deposit = useCallback( - async (token: LsErc20TokenId, amount: BN) => { + async (tokenId: LsErc20TokenId, amount: BN) => { // TODO: Should the user balance check be done here or assume that the consumer of the hook will handle that? assert( @@ -61,7 +59,7 @@ const useLiquifierDeposit = () => { 'Should not be able to call this function if the requirements are not ready yet', ); - const tokenDef = LS_ERC20_TOKEN_MAP[token]; + const tokenDef = LS_ERC20_TOKEN_MAP[tokenId]; notifyApproveProcessing(); diff --git a/apps/tangle-dapp/data/liquifier/useLiquifierErc20Balance.ts b/apps/tangle-dapp/data/liquifier/useLiquifierErc20Balance.ts index 967dc9afe..50bf1bf49 100644 --- a/apps/tangle-dapp/data/liquifier/useLiquifierErc20Balance.ts +++ b/apps/tangle-dapp/data/liquifier/useLiquifierErc20Balance.ts @@ -2,10 +2,8 @@ import { BN } from '@polkadot/util'; import { useEffect, useState } from 'react'; import { erc20Abi } from 'viem'; -import { - LS_ERC20_TOKEN_MAP, - LsErc20TokenId, -} from '../../constants/liquidStaking/liquidStakingEvm'; +import { LS_ERC20_TOKEN_MAP } from '../../constants/liquidStaking/liquidStakingErc20'; +import { LsErc20TokenId } from '../../constants/liquidStaking/types'; import useEvmAddress20 from '../../hooks/useEvmAddress'; import useContract from './useContract'; diff --git a/apps/tangle-dapp/data/liquifier/useLiquifierTgBalance.ts b/apps/tangle-dapp/data/liquifier/useLiquifierTgBalance.ts index e5a64badb..7896163f2 100644 --- a/apps/tangle-dapp/data/liquifier/useLiquifierTgBalance.ts +++ b/apps/tangle-dapp/data/liquifier/useLiquifierTgBalance.ts @@ -1,11 +1,9 @@ import { BN } from '@polkadot/util'; import { useEffect, useState } from 'react'; -import { - LS_ERC20_TOKEN_MAP, - LsErc20TokenId, -} from '../../constants/liquidStaking/liquidStakingEvm'; +import { LS_ERC20_TOKEN_MAP } from '../../constants/liquidStaking/liquidStakingErc20'; import liquifierTgTokenAbi from '../../constants/liquidStaking/liquifierTgTokenAbi'; +import { LsErc20TokenId } from '../../constants/liquidStaking/types'; import useEvmAddress20 from '../../hooks/useEvmAddress'; import useContract from './useContract'; diff --git a/apps/tangle-dapp/types/liquidStaking.ts b/apps/tangle-dapp/types/liquidStaking.ts index 736f98216..3013b6845 100644 --- a/apps/tangle-dapp/types/liquidStaking.ts +++ b/apps/tangle-dapp/types/liquidStaking.ts @@ -1,13 +1,13 @@ import { BN } from '@polkadot/util'; -import { LsParachainChainId } from '../constants/liquidStaking/liquidStakingParachain'; +import { LsProtocolId } from '../constants/liquidStaking/types'; // All chains export type StakingItem = { id: string; // address - Validator, contract address - DAPP, pool/vault ID - VaultOrStakePool totalValueStaked: BN; minimumStake?: BN; - chainId: LsParachainChainId; + chainId: LsProtocolId; chainDecimals: number; chainTokenSymbol: string; itemType: LiquidStakingItem; diff --git a/apps/tangle-dapp/utils/liquidStaking/isLsParachainToken.ts b/apps/tangle-dapp/utils/liquidStaking/isLsParachainToken.ts index 376c0e38f..6cba79fda 100644 --- a/apps/tangle-dapp/utils/liquidStaking/isLsParachainToken.ts +++ b/apps/tangle-dapp/utils/liquidStaking/isLsParachainToken.ts @@ -1,11 +1,7 @@ -import { LsParachainToken } from '../../constants/liquidStaking/liquidStakingParachain'; +import { LsToken } from '../../constants/liquidStaking/types'; -function isLsParachainToken( - tokenSymbol: string, -): tokenSymbol is LsParachainToken { - return Object.values(LsParachainToken).includes( - tokenSymbol as LsParachainToken, - ); +function isLsParachainToken(tokenSymbol: string): tokenSymbol is LsToken { + return Object.values(LsToken).includes(tokenSymbol as LsToken); } export default isLsParachainToken; From 5ae242633dc6d2d9c17dd2915aa2d9f159e8dce0 Mon Sep 17 00:00:00 2001 From: yurixander <101931215+yurixander@users.noreply.github.com> Date: Tue, 13 Aug 2024 21:08:22 -0400 Subject: [PATCH 12/60] refactor(tangle-dapp): Rename `chainId` to `protocolId` --- apps/tangle-dapp/app/liquid-staking/page.tsx | 2 +- .../stakeAndUnstake/LiquidStakeCard.tsx | 6 +-- .../stakeAndUnstake/LiquidStakingInput.tsx | 48 +++++++++++-------- .../LiquidStakingTokenItem.tsx | 8 ++-- .../stakeAndUnstake/LiquidUnstakeCard.tsx | 6 +-- .../ParachainWalletBalance.tsx | 4 +- .../{ChainLogo.tsx => ProtocolLogo.tsx} | 34 ++++++------- .../stakeAndUnstake/SelectTokenModal.tsx | 4 +- .../stakeAndUnstake/TokenChip.tsx | 6 +-- .../liquidStaking/liquidStakingErc20.ts | 9 ++++ .../constants/liquidStaking/types.ts | 3 +- 11 files changed, 73 insertions(+), 57 deletions(-) rename apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/{ChainLogo.tsx => ProtocolLogo.tsx} (72%) diff --git a/apps/tangle-dapp/app/liquid-staking/page.tsx b/apps/tangle-dapp/app/liquid-staking/page.tsx index 83ac13254..c164ebf65 100644 --- a/apps/tangle-dapp/app/liquid-staking/page.tsx +++ b/apps/tangle-dapp/app/liquid-staking/page.tsx @@ -38,7 +38,7 @@ const LiquidStakingPage: FC = () => { return ( { const selectedProtocol = getLsProtocolDef(selectedChainId); useSearchParamSync({ - key: LsSearchParamKey.CHAIN_ID, + key: LsSearchParamKey.PROTOCOL_ID, value: selectedChainId, parse: (value) => z.nativeEnum(LsProtocolId).parse(parseInt(value)), stringify: (value) => value.toString(), @@ -132,7 +132,7 @@ const LiquidStakeCard: FC = () => { <> { = ({ placeholder = '0', isTokenLiquidVariant = false, rightElement, - chainId, + protocolId, token, minAmount, maxAmount, @@ -109,7 +109,10 @@ const LiquidStakingInput: FC = ({ )} >
          - + {rightElement}
          @@ -144,30 +147,30 @@ const LiquidStakingInput: FC = ({ ); }; -type ChainSelectorProps = { - selectedChainId: LsProtocolId; +type ProtocolSelectorProps = { + selectedProtocolId: LsProtocolId; /** * If this function is not provided, the selector will be * considered read-only. */ - setChainId?: (newChain: LsProtocolId) => void; + setProtocolId?: (newProtocolId: LsProtocolId) => void; }; /** @internal */ -const ChainSelector: FC = ({ - selectedChainId, - setChainId, +const ProtocolSelector: FC = ({ + selectedProtocolId, + setProtocolId, }) => { - const isReadOnly = setChainId === undefined; + const isReadOnly = setProtocolId === undefined; const base = (
          - + - {getLsProtocolDef(selectedChainId).networkName} + {getLsProtocolDef(selectedProtocolId).networkName}
          @@ -175,7 +178,7 @@ const ChainSelector: FC = ({
          ); - return setChainId !== undefined ? ( + return setProtocolId !== undefined ? ( {base} @@ -184,18 +187,21 @@ const ChainSelector: FC = ({
            {Object.values(LsProtocolId) .filter( - (chainId): chainId is LsProtocolId => - chainId !== selectedChainId && typeof chainId !== 'string', + (protocolId): protocolId is LsProtocolId => + protocolId !== selectedProtocolId && + typeof protocolId !== 'string', ) - .map((chainId) => { + .map((protocolId) => { return ( -
          • +
          • } - onSelect={() => setChainId(chainId)} + leftIcon={ + + } + onSelect={() => setProtocolId(protocolId)} className="px-3 normal-case" > - {getLsProtocolDef(chainId).networkName} + {getLsProtocolDef(protocolId).networkName}
          • ); diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakingTokenItem.tsx b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakingTokenItem.tsx index 273617326..6137a3e1e 100644 --- a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakingTokenItem.tsx +++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakingTokenItem.tsx @@ -16,10 +16,10 @@ import { import { PagePath } from '../../../types'; import formatTangleBalance from '../../../utils/formatTangleBalance'; import StatItem from '../StatItem'; -import ChainLogo from './ChainLogo'; +import ProtocolLogo from './ProtocolLogo'; export type LiquidStakingTokenItemProps = { - chainId: LsProtocolId; + protocolId: LsProtocolId; title: string; tokenSymbol: LsToken; totalValueStaked: number; @@ -28,7 +28,7 @@ export type LiquidStakingTokenItemProps = { const LiquidStakingTokenItem: FC = ({ title, - chainId, + protocolId, tokenSymbol, totalValueStaked, totalStaked, @@ -47,7 +47,7 @@ const LiquidStakingTokenItem: FC = ({
            - + { const [selectedChainId, setSelectedChainId] = useSearchParamState({ - key: LsSearchParamKey.CHAIN_ID, + key: LsSearchParamKey.PROTOCOL_ID, defaultValue: LsProtocolId.TANGLE_RESTAKING_PARACHAIN, parser: (value) => z.nativeEnum(LsProtocolId).parse(parseInt(value)), stringify: (value) => value.toString(), @@ -166,7 +166,7 @@ const LiquidUnstakeCard: FC = () => { {/* TODO: Have a way to trigger a refresh of the amount once the wallet balance (max) button is clicked. Need to signal to the liquid staking input to update its display amount based on the `fromAmount` prop. */} { { +const getSizeNumber = (size: ProtocolLogoSize) => { switch (size) { case 'sm': return 24; @@ -25,7 +25,7 @@ const getSizeNumber = (size: ChainLogoSize) => { } }; -const getSizeClass = (size: ChainLogoSize) => { +const getSizeClass = (size: ProtocolLogoSize) => { switch (size) { case 'sm': return 'min-w-[24px] min-h-[24px]'; @@ -34,8 +34,8 @@ const getSizeClass = (size: ChainLogoSize) => { } }; -const getBackgroundColor = (chain: LsProtocolId) => { - switch (chain) { +const getBackgroundColor = (protocolId: LsProtocolId) => { + switch (protocolId) { case LsProtocolId.MANTA: return 'bg-[#13101D] dark:bg-[#13101D]'; case LsProtocolId.MOONBEAM: @@ -55,8 +55,8 @@ const getBackgroundColor = (chain: LsProtocolId) => { } }; -const ChainLogo: FC = ({ - chainId, +const ProtocolLogo: FC = ({ + protocolId, size = 'md', isRounded = false, isLiquidVariant = false, @@ -64,7 +64,7 @@ const ChainLogo: FC = ({ const sizeNumber = getSizeNumber(size); // In case the chain id is not provided, render a placeholder. - if (chainId === undefined) { + if (protocolId === undefined) { return (
            = ({ {`Logo @@ -97,4 +97,4 @@ const ChainLogo: FC = ({ ); }; -export default ChainLogo; +export default ProtocolLogo; diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/SelectTokenModal.tsx b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/SelectTokenModal.tsx index 1d628e8a5..2cd4c942c 100644 --- a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/SelectTokenModal.tsx +++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/SelectTokenModal.tsx @@ -12,7 +12,7 @@ import { LsProtocolId } from '../../../constants/liquidStaking/types'; import { AnySubstrateAddress } from '../../../types/utils'; import formatBn from '../../../utils/formatBn'; import AddressLink from '../AddressLink'; -import ChainLogo from './ChainLogo'; +import ProtocolLogo from './ProtocolLogo'; export type SelectTokenModalProps = { isOpen: boolean; @@ -106,7 +106,7 @@ const TokenListItem: FC = ({ > {/* Information */}
            - +
            diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/TokenChip.tsx b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/TokenChip.tsx index 60a9cfce4..cef7f2c7f 100644 --- a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/TokenChip.tsx +++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/TokenChip.tsx @@ -8,7 +8,7 @@ import { LST_PREFIX, LsToken, } from '../../../constants/liquidStaking/types'; -import ChainLogo from './ChainLogo'; +import ProtocolLogo from './ProtocolLogo'; import DropdownChevronIcon from './DropdownChevronIcon'; type TokenChipProps = { @@ -41,9 +41,9 @@ const TokenChip: FC = ({ token, isLiquidVariant, onClick }) => { onClick !== undefined && 'cursor-pointer', )} > - diff --git a/apps/tangle-dapp/constants/liquidStaking/liquidStakingErc20.ts b/apps/tangle-dapp/constants/liquidStaking/liquidStakingErc20.ts index 68ed777e8..a6178bf43 100644 --- a/apps/tangle-dapp/constants/liquidStaking/liquidStakingErc20.ts +++ b/apps/tangle-dapp/constants/liquidStaking/liquidStakingErc20.ts @@ -1,3 +1,4 @@ +import { StaticAssetPath } from '..'; import { LsErc20TokenId, LsProtocolId, LsToken } from './types'; import { LsErc20TokenDef } from './types'; @@ -6,6 +7,8 @@ const ChainlinkErc20TokenDef: LsErc20TokenDef = { id: LsProtocolId.CHAINLINK, name: 'Chainlink', networkName: 'Ethereum Mainnet', + // TODO: Add logo and link it here. + logo: StaticAssetPath.LIQUID_STAKING_TOKEN_PHALA, token: LsToken.LINK, decimals: 18, // TODO: Use Liquifier's testnet address if the environment is development. @@ -20,6 +23,8 @@ const TheGraphErc20TokenDef: LsErc20TokenDef = { id: LsProtocolId.THE_GRAPH, name: 'The Graph', networkName: 'Ethereum Mainnet', + // TODO: Add logo and link it here. + logo: StaticAssetPath.LIQUID_STAKING_TOKEN_PHALA, token: LsToken.GRT, decimals: 18, // TODO: Use Liquifier's testnet address if the environment is development. @@ -34,6 +39,8 @@ const LivepeerErc20TokenDef: LsErc20TokenDef = { id: LsProtocolId.LIVEPEER, name: 'Livepeer', networkName: 'Ethereum Mainnet', + // TODO: Add logo and link it here. + logo: StaticAssetPath.LIQUID_STAKING_TOKEN_PHALA, token: LsToken.LPT, decimals: 18, // TODO: Use Liquifier's testnet address if the environment is development. @@ -48,6 +55,8 @@ const PolygonErc20TokenDef: LsErc20TokenDef = { id: LsProtocolId.POLYGON, name: 'Polygon', networkName: 'Ethereum Mainnet', + // TODO: Add logo and link it here. + logo: StaticAssetPath.LIQUID_STAKING_TOKEN_PHALA, token: LsToken.POL, decimals: 18, // TODO: Use Liquifier's testnet address if the environment is development. diff --git a/apps/tangle-dapp/constants/liquidStaking/types.ts b/apps/tangle-dapp/constants/liquidStaking/types.ts index 6cd16edc4..c94bed73c 100644 --- a/apps/tangle-dapp/constants/liquidStaking/types.ts +++ b/apps/tangle-dapp/constants/liquidStaking/types.ts @@ -77,6 +77,7 @@ export type LsErc20TokenDef = { networkName: string; decimals: number; token: LsErc20Token; + logo: StaticAssetPath; address: HexString; liquifierAdapterAddress: HexString; liquifierTgTokenAddress: HexString; @@ -91,7 +92,7 @@ export type LsCardSearchParams = { export enum LsSearchParamKey { AMOUNT = 'amount', - CHAIN_ID = 'chainId', + PROTOCOL_ID = 'protocol', ACTION = 'action', } From a8fe2eb800a9671191b6134de250165369dcbee5 Mon Sep 17 00:00:00 2001 From: yurixander <101931215+yurixander@users.noreply.github.com> Date: Wed, 14 Aug 2024 19:55:00 -0400 Subject: [PATCH 13/60] refactor(tangle-dapp): Naming --- .../liquidStaking/liquidStakingErc20.ts | 16 ++++++++-------- .../constants/liquidStaking/liquifierAbi.ts | 4 ++-- .../liquidStaking/liquifierAdapterAbi.ts | 4 ++-- .../liquidStaking/liquifierTgTokenAbi.ts | 4 ++-- .../tangle-dapp/constants/liquidStaking/types.ts | 2 +- .../data/liquifier/useLiquifierDeposit.ts | 4 ++-- .../data/liquifier/useLiquifierTgBalance.ts | 4 ++-- 7 files changed, 19 insertions(+), 19 deletions(-) diff --git a/apps/tangle-dapp/constants/liquidStaking/liquidStakingErc20.ts b/apps/tangle-dapp/constants/liquidStaking/liquidStakingErc20.ts index a6178bf43..44b1b2f9a 100644 --- a/apps/tangle-dapp/constants/liquidStaking/liquidStakingErc20.ts +++ b/apps/tangle-dapp/constants/liquidStaking/liquidStakingErc20.ts @@ -2,7 +2,7 @@ import { StaticAssetPath } from '..'; import { LsErc20TokenId, LsProtocolId, LsToken } from './types'; import { LsErc20TokenDef } from './types'; -const ChainlinkErc20TokenDef: LsErc20TokenDef = { +const CHAINLINK: LsErc20TokenDef = { type: 'erc20', id: LsProtocolId.CHAINLINK, name: 'Chainlink', @@ -18,7 +18,7 @@ const ChainlinkErc20TokenDef: LsErc20TokenDef = { liquifierTgTokenAddress: '0x', }; -const TheGraphErc20TokenDef: LsErc20TokenDef = { +const THE_GRAPH: LsErc20TokenDef = { type: 'erc20', id: LsProtocolId.THE_GRAPH, name: 'The Graph', @@ -34,7 +34,7 @@ const TheGraphErc20TokenDef: LsErc20TokenDef = { liquifierTgTokenAddress: '0x', }; -const LivepeerErc20TokenDef: LsErc20TokenDef = { +const LIVEPEER: LsErc20TokenDef = { type: 'erc20', id: LsProtocolId.LIVEPEER, name: 'Livepeer', @@ -50,7 +50,7 @@ const LivepeerErc20TokenDef: LsErc20TokenDef = { liquifierTgTokenAddress: '0x', }; -const PolygonErc20TokenDef: LsErc20TokenDef = { +const POLYGON: LsErc20TokenDef = { type: 'erc20', id: LsProtocolId.POLYGON, name: 'Polygon', @@ -67,8 +67,8 @@ const PolygonErc20TokenDef: LsErc20TokenDef = { }; export const LS_ERC20_TOKEN_MAP: Record = { - [LsProtocolId.CHAINLINK]: ChainlinkErc20TokenDef, - [LsProtocolId.THE_GRAPH]: TheGraphErc20TokenDef, - [LsProtocolId.LIVEPEER]: LivepeerErc20TokenDef, - [LsProtocolId.POLYGON]: PolygonErc20TokenDef, + [LsProtocolId.CHAINLINK]: CHAINLINK, + [LsProtocolId.THE_GRAPH]: THE_GRAPH, + [LsProtocolId.LIVEPEER]: LIVEPEER, + [LsProtocolId.POLYGON]: POLYGON, }; diff --git a/apps/tangle-dapp/constants/liquidStaking/liquifierAbi.ts b/apps/tangle-dapp/constants/liquidStaking/liquifierAbi.ts index 9f6ae7c51..af9ab6971 100644 --- a/apps/tangle-dapp/constants/liquidStaking/liquifierAbi.ts +++ b/apps/tangle-dapp/constants/liquidStaking/liquifierAbi.ts @@ -1,6 +1,6 @@ import { Abi } from 'viem'; -const liquifierAbi = [ +const LIQUIFIER_ABI = [ { type: 'constructor', stateMutability: 'nonpayable', @@ -187,4 +187,4 @@ const liquifierAbi = [ }, ] as const satisfies Abi; -export default liquifierAbi; +export default LIQUIFIER_ABI; diff --git a/apps/tangle-dapp/constants/liquidStaking/liquifierAdapterAbi.ts b/apps/tangle-dapp/constants/liquidStaking/liquifierAdapterAbi.ts index 198082b11..cca7edef0 100644 --- a/apps/tangle-dapp/constants/liquidStaking/liquifierAdapterAbi.ts +++ b/apps/tangle-dapp/constants/liquidStaking/liquifierAdapterAbi.ts @@ -1,6 +1,6 @@ import { Abi } from 'viem'; -const liquifierAdapterAbi = [ +const LIQUIFIER_ADAPTER_ABI = [ { constant: false, inputs: [ @@ -343,4 +343,4 @@ const liquifierAdapterAbi = [ }, ] as const satisfies Abi; -export default liquifierAdapterAbi; +export default LIQUIFIER_ADAPTER_ABI; diff --git a/apps/tangle-dapp/constants/liquidStaking/liquifierTgTokenAbi.ts b/apps/tangle-dapp/constants/liquidStaking/liquifierTgTokenAbi.ts index 929c301c0..1bea1272e 100644 --- a/apps/tangle-dapp/constants/liquidStaking/liquifierTgTokenAbi.ts +++ b/apps/tangle-dapp/constants/liquidStaking/liquifierTgTokenAbi.ts @@ -1,6 +1,6 @@ import { Abi } from 'viem'; -const liquifierTgTokenAbi = [ +const LIQUIFIER_TG_TOKEN_ABI = [ { inputs: [], name: 'decimals', @@ -288,4 +288,4 @@ const liquifierTgTokenAbi = [ }, ] as const satisfies Abi; -export default liquifierTgTokenAbi; +export default LIQUIFIER_TG_TOKEN_ABI; diff --git a/apps/tangle-dapp/constants/liquidStaking/types.ts b/apps/tangle-dapp/constants/liquidStaking/types.ts index c94bed73c..cffc98103 100644 --- a/apps/tangle-dapp/constants/liquidStaking/types.ts +++ b/apps/tangle-dapp/constants/liquidStaking/types.ts @@ -60,7 +60,7 @@ export const LS_ERC20_TOKEN_IDS = [ export type LsParachainChainDef = { type: 'parachain'; - id: LsProtocolId; + id: LsParachainChainId; name: string; token: LsParachainToken; logo: StaticAssetPath; diff --git a/apps/tangle-dapp/data/liquifier/useLiquifierDeposit.ts b/apps/tangle-dapp/data/liquifier/useLiquifierDeposit.ts index da880f034..ebefa75cb 100644 --- a/apps/tangle-dapp/data/liquifier/useLiquifierDeposit.ts +++ b/apps/tangle-dapp/data/liquifier/useLiquifierDeposit.ts @@ -5,7 +5,7 @@ import { erc20Abi } from 'viem'; import { TxName } from '../../constants'; import { LS_ERC20_TOKEN_MAP } from '../../constants/liquidStaking/liquidStakingErc20'; -import liquifierAbi from '../../constants/liquidStaking/liquifierAbi'; +import LIQUIFIER_ABI from '../../constants/liquidStaking/liquifierAbi'; import { LsErc20TokenId } from '../../constants/liquidStaking/types'; import useEvmAddress20 from '../../hooks/useEvmAddress'; import useTxNotification from '../../hooks/useTxNotification'; @@ -31,7 +31,7 @@ import useContract from './useContract'; const useLiquifierDeposit = () => { const activeEvmAddress20 = useEvmAddress20(); const { write: writeChainlinkErc20 } = useContract(erc20Abi); - const { write: writeLiquifier } = useContract(liquifierAbi); + const { write: writeLiquifier } = useContract(LIQUIFIER_ABI); const { notifyProcessing: notifyApproveProcessing, diff --git a/apps/tangle-dapp/data/liquifier/useLiquifierTgBalance.ts b/apps/tangle-dapp/data/liquifier/useLiquifierTgBalance.ts index 7896163f2..3e37d721d 100644 --- a/apps/tangle-dapp/data/liquifier/useLiquifierTgBalance.ts +++ b/apps/tangle-dapp/data/liquifier/useLiquifierTgBalance.ts @@ -2,14 +2,14 @@ import { BN } from '@polkadot/util'; import { useEffect, useState } from 'react'; import { LS_ERC20_TOKEN_MAP } from '../../constants/liquidStaking/liquidStakingErc20'; -import liquifierTgTokenAbi from '../../constants/liquidStaking/liquifierTgTokenAbi'; +import LIQUIFIER_TG_TOKEN_ABI from '../../constants/liquidStaking/liquifierTgTokenAbi'; import { LsErc20TokenId } from '../../constants/liquidStaking/types'; import useEvmAddress20 from '../../hooks/useEvmAddress'; import useContract from './useContract'; const useLiquifierTgBalance = (tokenId: LsErc20TokenId) => { const activeEvmAddress20 = useEvmAddress20(); - const { read } = useContract(liquifierTgTokenAbi); + const { read } = useContract(LIQUIFIER_TG_TOKEN_ABI); const [balance, setBalance] = useState(null); useEffect(() => { From f041c9b6247937337f08add1d56efd8d4581012a Mon Sep 17 00:00:00 2001 From: yurixander <101931215+yurixander@users.noreply.github.com> Date: Fri, 16 Aug 2024 21:15:35 -0400 Subject: [PATCH 14/60] feat(tangle-dapp): Implement `AgnosticLsBalance` and `useAgnosticLsBalance` --- .../stakeAndUnstake/AgnosticLsBalance.tsx | 120 ++++++++++++++++++ .../stakeAndUnstake/LiquidUnstakeCard.tsx | 6 +- .../stakeAndUnstake/useAgnosticLsBalance.ts | 85 +++++++++++++ .../tangle-dapp/data/liquifier/useContract.ts | 2 + .../utils/liquidStaking/isLsErc20TokenId.ts | 10 ++ .../utils/liquidStaking/isLsParachainToken.ts | 6 +- 6 files changed, 224 insertions(+), 5 deletions(-) create mode 100644 apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/AgnosticLsBalance.tsx create mode 100644 apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/useAgnosticLsBalance.ts create mode 100644 apps/tangle-dapp/utils/liquidStaking/isLsErc20TokenId.ts diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/AgnosticLsBalance.tsx b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/AgnosticLsBalance.tsx new file mode 100644 index 000000000..5fd315a78 --- /dev/null +++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/AgnosticLsBalance.tsx @@ -0,0 +1,120 @@ +'use client'; + +import { WalletFillIcon, WalletLineIcon } from '@webb-tools/icons'; +import { + SkeletonLoader, + Tooltip, + TooltipBody, + TooltipTrigger, + Typography, +} from '@webb-tools/webb-ui-components'; +import { FC, useCallback, useMemo, useState } from 'react'; +import { twMerge } from 'tailwind-merge'; + +import { EMPTY_VALUE_PLACEHOLDER } from '../../../constants'; +import { LsProtocolId } from '../../../constants/liquidStaking/types'; +import formatBn from '../../../utils/formatBn'; +import useAgnosticLsBalance from './useAgnosticLsBalance'; + +export type AgnosticLsBalanceProps = { + isNative?: boolean; + protocolId: LsProtocolId; + decimals: number; + tooltip?: string; + onlyShowTooltipWhenBalanceIsSet?: boolean; + onClick?: () => void; +}; + +const AgnosticLsBalance: FC = ({ + isNative = true, + protocolId, + decimals, + tooltip, + onlyShowTooltipWhenBalanceIsSet = true, + onClick, +}) => { + const [isHovering, setIsHovering] = useState(false); + const balance = useAgnosticLsBalance(isNative, protocolId); + + const formattedBalance = useMemo(() => { + // No account is active; display a placeholder instead of a loading state. + if (balance === EMPTY_VALUE_PLACEHOLDER) { + return balance; + } + // Balance is still loading. + else if (balance === null) { + return null; + } + + return formatBn(balance, decimals, { + fractionMaxLength: undefined, + includeCommas: true, + }); + }, [balance, decimals]); + + const isClickable = + onlyShowTooltipWhenBalanceIsSet && + balance !== null && + typeof balance !== 'string' && + !balance.isZero(); + + const handleClick = useCallback(() => { + if (!isClickable || onClick === undefined) { + return; + } + + onClick(); + }, [isClickable, onClick]); + + const content = ( +
            setIsHovering(true)} + onMouseLeave={() => setIsHovering(false)} + className={twMerge( + 'group flex gap-1 items-center justify-center', + isClickable && 'cursor-pointer', + )} + > + {isHovering && isClickable ? ( + + ) : ( + + )} + + {formattedBalance === null ? ( + + ) : ( + + {formattedBalance} + + )} +
            + ); + + if (tooltip === undefined || !isClickable) { + return content; + } + + // Otherwise, the tooltip is set and it should be shown. + return ( + + {content} + + + {tooltip} + + + ); +}; + +export default AgnosticLsBalance; diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidUnstakeCard.tsx b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidUnstakeCard.tsx index 203fb4738..88496ecb3 100644 --- a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidUnstakeCard.tsx +++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidUnstakeCard.tsx @@ -28,10 +28,10 @@ import useApiRx from '../../../hooks/useApiRx'; import useSearchParamState from '../../../hooks/useSearchParamState'; import useSearchParamSync from '../../../hooks/useSearchParamSync'; import { TxStatus } from '../../../hooks/useSubstrateTx'; +import AgnosticLsBalance from './AgnosticLsBalance'; import ExchangeRateDetailItem from './ExchangeRateDetailItem'; import LiquidStakingInput from './LiquidStakingInput'; import MintAndRedeemFeeDetailItem from './MintAndRedeemFeeDetailItem'; -import ParachainWalletBalance from './ParachainWalletBalance'; import SelectTokenModal from './SelectTokenModal'; import UnstakePeriodDetailItem from './UnstakePeriodDetailItem'; import UnstakeRequestSubmittedModal from './UnstakeRequestSubmittedModal'; @@ -152,9 +152,9 @@ const LiquidUnstakeCard: FC = () => { }, [redeemTxStatus]); const stakedWalletBalance = ( - setFromAmount(maximumInputAmount)} diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/useAgnosticLsBalance.ts b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/useAgnosticLsBalance.ts new file mode 100644 index 000000000..b90d82d38 --- /dev/null +++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/useAgnosticLsBalance.ts @@ -0,0 +1,85 @@ +import { BN, BN_ZERO } from '@polkadot/util'; +import assert from 'assert'; +import { useEffect, useState } from 'react'; +import { erc20Abi } from 'viem'; + +import { EMPTY_VALUE_PLACEHOLDER } from '../../../constants'; +import { LS_ERC20_TOKEN_MAP } from '../../../constants/liquidStaking/liquidStakingErc20'; +import LIQUIFIER_TG_TOKEN_ABI from '../../../constants/liquidStaking/liquifierTgTokenAbi'; +import { LsProtocolId } from '../../../constants/liquidStaking/types'; +import useParachainBalances from '../../../data/liquidStaking/useParachainBalances'; +import useContract from '../../../data/liquifier/useContract'; +import useEvmAddress20 from '../../../hooks/useEvmAddress'; +import useSubstrateAddress from '../../../hooks/useSubstrateAddress'; +import isLsErc20TokenId from '../../../utils/liquidStaking/isLsErc20TokenId'; +import isLsParachainToken from '../../../utils/liquidStaking/isLsParachainToken'; + +const useAgnosticLsBalance = (isNative: boolean, protocolId: LsProtocolId) => { + const substrateAddress = useSubstrateAddress(); + const evmAddress20 = useEvmAddress20(); + const { nativeBalances, liquidBalances } = useParachainBalances(); + const { read: readErc20 } = useContract(erc20Abi); + const { read: readLiquidErc20 } = useContract(LIQUIFIER_TG_TOKEN_ABI); + + const [balance, setBalance] = useState< + BN | null | typeof EMPTY_VALUE_PLACEHOLDER + >(EMPTY_VALUE_PLACEHOLDER); + + const parachainBalances = isNative ? nativeBalances : liquidBalances; + const isAccountConnected = substrateAddress !== null || evmAddress20 !== null; + + useEffect(() => { + // Account is not connected. Reset balance to a placeholder. + if (!isAccountConnected) { + setBalance(EMPTY_VALUE_PLACEHOLDER); + + return; + } else if (isLsErc20TokenId(protocolId)) { + // Can't determine ERC20 balance without an active EVM account. + if (evmAddress20 === null) { + setBalance(EMPTY_VALUE_PLACEHOLDER); + + return; + } + + const tokenDef = LS_ERC20_TOKEN_MAP[protocolId]; + const target = isNative ? readErc20 : readLiquidErc20; + + // Still loading. + if (target === null) { + setBalance(null); + + return; + } + + // TODO: This only loads the balance once. Make it so it updates every few seconds that way the it responds to any balance changes that may occur, not just when loading the site initially. Like a subscription. + target({ + address: tokenDef.address, + functionName: 'balanceOf', + args: [evmAddress20], + }).then((result) => setBalance(new BN(result.toString()))); + } else { + if (parachainBalances === null) { + return; + } + + assert(isLsParachainToken(protocolId)); + + const newBalance = parachainBalances.get(protocolId) ?? BN_ZERO; + + setBalance(newBalance); + } + }, [ + evmAddress20, + isAccountConnected, + isNative, + parachainBalances, + protocolId, + readErc20, + readLiquidErc20, + ]); + + return balance; +}; + +export default useAgnosticLsBalance; diff --git a/apps/tangle-dapp/data/liquifier/useContract.ts b/apps/tangle-dapp/data/liquifier/useContract.ts index db3234c8c..1de621de0 100644 --- a/apps/tangle-dapp/data/liquifier/useContract.ts +++ b/apps/tangle-dapp/data/liquifier/useContract.ts @@ -97,6 +97,8 @@ const useContract = (abi: Abi) => { [abi, activeEvmAddress20, connectorClient], ); + // TODO: This only loads the balance once. Make it so it updates every few seconds that way the it responds to any balance changes that may occur, not just when loading the site initially. Like a subscription. So, add an extra function like `readStream` or `subscribe` that will allow the user to subscribe to the contract and get updates whenever the contract changes. + return { // Only provide the read functions once the public client is ready. read: publicClient === null ? null : read, diff --git a/apps/tangle-dapp/utils/liquidStaking/isLsErc20TokenId.ts b/apps/tangle-dapp/utils/liquidStaking/isLsErc20TokenId.ts new file mode 100644 index 000000000..daf73e76a --- /dev/null +++ b/apps/tangle-dapp/utils/liquidStaking/isLsErc20TokenId.ts @@ -0,0 +1,10 @@ +import { + LS_ERC20_TOKEN_IDS, + LsErc20TokenId, +} from '../../constants/liquidStaking/types'; + +function isLsErc20TokenId(tokenId: number): tokenId is LsErc20TokenId { + return LS_ERC20_TOKEN_IDS.includes(tokenId); +} + +export default isLsErc20TokenId; diff --git a/apps/tangle-dapp/utils/liquidStaking/isLsParachainToken.ts b/apps/tangle-dapp/utils/liquidStaking/isLsParachainToken.ts index 6cba79fda..df6686a3d 100644 --- a/apps/tangle-dapp/utils/liquidStaking/isLsParachainToken.ts +++ b/apps/tangle-dapp/utils/liquidStaking/isLsParachainToken.ts @@ -1,6 +1,8 @@ -import { LsToken } from '../../constants/liquidStaking/types'; +import { LsProtocolId, LsToken } from '../../constants/liquidStaking/types'; -function isLsParachainToken(tokenSymbol: string): tokenSymbol is LsToken { +function isLsParachainToken( + tokenSymbol: string | LsProtocolId, +): tokenSymbol is LsToken { return Object.values(LsToken).includes(tokenSymbol as LsToken); } From 336825375f9889038f8062f4d74e84c4061131ac Mon Sep 17 00:00:00 2001 From: yurixander <101931215+yurixander@users.noreply.github.com> Date: Sun, 18 Aug 2024 18:43:46 -0400 Subject: [PATCH 15/60] feat(tangle-dapp): Create `usePolling` hook, progress on `useExchangeRate` --- .../app/liquid-staking/[tokenSymbol]/page.tsx | 1 + .../ExchangeRateDetailItem.tsx | 20 +++-- .../stakeAndUnstake/LiquidStakeCard.tsx | 59 ++++--------- .../stakeAndUnstake/LiquidUnstakeCard.tsx | 54 +++--------- .../stakeAndUnstake/useAgnosticLsBalance.ts | 12 ++- .../stakeAndUnstake/useLsSpendingLimits.ts | 66 +++++++++++++++ .../constants/liquidStaking/types.ts | 15 ++++ .../data/liquidStaking/useExchangeRate.ts | 84 ++++++++++++++++++- .../data/liquidStaking/usePolling.ts | 35 ++++++++ .../liquidStaking/isLsParachainChainId.ts | 13 +++ .../utils/liquidStaking/isLsParachainToken.ts | 10 ++- 11 files changed, 264 insertions(+), 105 deletions(-) create mode 100644 apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/useLsSpendingLimits.ts create mode 100644 apps/tangle-dapp/data/liquidStaking/usePolling.ts create mode 100644 apps/tangle-dapp/utils/liquidStaking/isLsParachainChainId.ts diff --git a/apps/tangle-dapp/app/liquid-staking/[tokenSymbol]/page.tsx b/apps/tangle-dapp/app/liquid-staking/[tokenSymbol]/page.tsx index 6ac078117..516edd05c 100644 --- a/apps/tangle-dapp/app/liquid-staking/[tokenSymbol]/page.tsx +++ b/apps/tangle-dapp/app/liquid-staking/[tokenSymbol]/page.tsx @@ -26,6 +26,7 @@ const LiquidStakingTokenPage: FC = ({ params: { tokenSymbol } }) => { stringify: (value) => (value ? 'stake' : 'unstake'), }); + // An invalid or unknown token symbol was provided on the URL. if (!isLsParachainToken(tokenSymbol)) { return notFound(); } diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/ExchangeRateDetailItem.tsx b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/ExchangeRateDetailItem.tsx index 0f2059fc1..fa8c0b314 100644 --- a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/ExchangeRateDetailItem.tsx +++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/ExchangeRateDetailItem.tsx @@ -1,29 +1,33 @@ import { SkeletonLoader } from '@webb-tools/webb-ui-components'; import { FC } from 'react'; -import { ParachainCurrency } from '../../../constants/liquidStaking/liquidStakingParachain'; -import { LST_PREFIX, LsToken } from '../../../constants/liquidStaking/types'; -import useExchangeRate, { - ExchangeRateType, -} from '../../../data/liquidStaking/useExchangeRate'; +import { + LsProtocolId, + LST_PREFIX, + LsToken, +} from '../../../constants/liquidStaking/types'; +import { ExchangeRateType } from '../../../data/liquidStaking/useExchangeRate'; +import useExchangeRate from '../../../data/liquidStaking/useExchangeRate'; import DetailItem from './DetailItem'; export type ExchangeRateDetailItemProps = { type: ExchangeRateType; token: LsToken; - currency: ParachainCurrency; + protocolId: LsProtocolId; }; const ExchangeRateDetailItem: FC = ({ type, token, - currency, + protocolId, }) => { - const exchangeRate = useExchangeRate(type, currency); + const { exchangeRate, isRefreshing } = useExchangeRate(type, protocolId); const exchangeRateElement = exchangeRate === null ? ( + ) : isRefreshing ? ( +
            {exchangeRate}
            ) : ( exchangeRate ); diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakeCard.tsx b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakeCard.tsx index 83db8f9dd..b715d2635 100644 --- a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakeCard.tsx +++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakeCard.tsx @@ -4,7 +4,7 @@ // the `lstMinting` pallet for this file only. import '@webb-tools/tangle-restaking-types'; -import { BN, BN_ZERO } from '@polkadot/util'; +import { BN } from '@polkadot/util'; import { ArrowDownIcon } from '@radix-ui/react-icons'; import { Search } from '@webb-tools/icons'; import { @@ -13,7 +13,6 @@ import { Input, Typography, } from '@webb-tools/webb-ui-components'; -import { TANGLE_RESTAKING_PARACHAIN_LOCAL_DEV_NETWORK } from '@webb-tools/webb-ui-components/constants/networks'; import React, { FC, useCallback, useMemo } from 'react'; import { z } from 'zod'; @@ -28,18 +27,16 @@ import useExchangeRate, { } from '../../../data/liquidStaking/useExchangeRate'; import { useLiquidStakingStore } from '../../../data/liquidStaking/useLiquidStakingStore'; import useMintTx from '../../../data/liquidStaking/useMintTx'; -import useParachainBalances from '../../../data/liquidStaking/useParachainBalances'; -import useApi from '../../../hooks/useApi'; -import useApiRx from '../../../hooks/useApiRx'; import useSearchParamState from '../../../hooks/useSearchParamState'; import useSearchParamSync from '../../../hooks/useSearchParamSync'; import { TxStatus } from '../../../hooks/useSubstrateTx'; +import AgnosticLsBalance from './AgnosticLsBalance'; import ExchangeRateDetailItem from './ExchangeRateDetailItem'; import LiquidStakingInput from './LiquidStakingInput'; import MintAndRedeemFeeDetailItem from './MintAndRedeemFeeDetailItem'; -import ParachainWalletBalance from './ParachainWalletBalance'; import SelectValidatorsButton from './SelectValidatorsButton'; import UnstakePeriodDetailItem from './UnstakePeriodDetailItem'; +import useLsSpendingLimits from './useLsSpendingLimits'; const LiquidStakeCard: FC = () => { const [fromAmount, setFromAmount] = useSearchParamState({ @@ -51,7 +48,11 @@ const LiquidStakeCard: FC = () => { const { selectedChainId, setSelectedChainId } = useLiquidStakingStore(); const { execute: executeMintTx, status: mintTxStatus } = useMintTx(); - const { nativeBalances } = useParachainBalances(); + + const { maxSpendable, minSpendable } = useLsSpendingLimits( + true, + selectedChainId, + ); const selectedProtocol = getLsProtocolDef(selectedChainId); @@ -63,43 +64,11 @@ const LiquidStakeCard: FC = () => { setValue: setSelectedChainId, }); - const exchangeRate = useExchangeRate( + const { exchangeRate } = useExchangeRate( ExchangeRateType.NativeToLiquid, selectedProtocol.currency, ); - const { result: minimumMintingAmount } = useApiRx( - useCallback( - (api) => - api.query.lstMinting.minimumMint({ - Native: selectedProtocol.currency, - }), - [selectedProtocol.currency], - ), - TANGLE_RESTAKING_PARACHAIN_LOCAL_DEV_NETWORK.wsRpcEndpoint, - ); - - const { result: existentialDepositAmount } = useApi( - useCallback((api) => api.consts.balances.existentialDeposit, []), - TANGLE_RESTAKING_PARACHAIN_LOCAL_DEV_NETWORK.wsRpcEndpoint, - ); - - const minimumInputAmount = useMemo(() => { - if (minimumMintingAmount === null || existentialDepositAmount === null) { - return null; - } - - return BN.max(minimumMintingAmount, existentialDepositAmount); - }, [existentialDepositAmount, minimumMintingAmount]); - - const maximumInputAmount = useMemo(() => { - if (nativeBalances === null) { - return null; - } - - return nativeBalances.get(selectedProtocol.token) ?? BN_ZERO; - }, [nativeBalances, selectedProtocol.token]); - const handleStakeClick = useCallback(() => { if (executeMintTx === null || fromAmount === null) { return; @@ -120,11 +89,11 @@ const LiquidStakeCard: FC = () => { }, [fromAmount, exchangeRate]); const walletBalance = ( - setFromAmount(maximumInputAmount)} + onClick={() => setFromAmount(maxSpendable)} /> ); @@ -140,8 +109,8 @@ const LiquidStakeCard: FC = () => { placeholder={`0 ${selectedProtocol.token}`} rightElement={walletBalance} setChainId={setSelectedChainId} - minAmount={minimumInputAmount ?? undefined} - maxAmount={maximumInputAmount ?? undefined} + minAmount={minSpendable ?? undefined} + maxAmount={maxSpendable ?? undefined} /> diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidUnstakeCard.tsx b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidUnstakeCard.tsx index 88496ecb3..d7997346e 100644 --- a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidUnstakeCard.tsx +++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidUnstakeCard.tsx @@ -4,10 +4,9 @@ // the `lstMinting` pallet for this file only. import '@webb-tools/tangle-restaking-types'; -import { BN, BN_ZERO } from '@polkadot/util'; +import { BN } from '@polkadot/util'; import { ArrowDownIcon } from '@radix-ui/react-icons'; import { Alert, Button } from '@webb-tools/webb-ui-components'; -import { TANGLE_RESTAKING_PARACHAIN_LOCAL_DEV_NETWORK } from '@webb-tools/webb-ui-components/constants/networks'; import { FC, useCallback, useEffect, useMemo, useState } from 'react'; import { z } from 'zod'; @@ -21,10 +20,7 @@ import useDelegationsOccupiedStatus from '../../../data/liquidStaking/useDelegat import useExchangeRate, { ExchangeRateType, } from '../../../data/liquidStaking/useExchangeRate'; -import useParachainBalances from '../../../data/liquidStaking/useParachainBalances'; import useRedeemTx from '../../../data/liquidStaking/useRedeemTx'; -import useApi from '../../../hooks/useApi'; -import useApiRx from '../../../hooks/useApiRx'; import useSearchParamState from '../../../hooks/useSearchParamState'; import useSearchParamSync from '../../../hooks/useSearchParamSync'; import { TxStatus } from '../../../hooks/useSubstrateTx'; @@ -35,6 +31,7 @@ import MintAndRedeemFeeDetailItem from './MintAndRedeemFeeDetailItem'; import SelectTokenModal from './SelectTokenModal'; import UnstakePeriodDetailItem from './UnstakePeriodDetailItem'; import UnstakeRequestSubmittedModal from './UnstakeRequestSubmittedModal'; +import useLsSpendingLimits from './useLsSpendingLimits'; const LiquidUnstakeCard: FC = () => { const [isSelectTokenModalOpen, setIsSelectTokenModalOpen] = useState(false); @@ -57,13 +54,16 @@ const LiquidUnstakeCard: FC = () => { txHash: redeemTxHash, } = useRedeemTx(); - const { liquidBalances } = useParachainBalances(); + const { minSpendable, maxSpendable } = useLsSpendingLimits( + false, + selectedChainId, + ); const selectedProtocol = getLsProtocolDef(selectedChainId); - const exchangeRate = useExchangeRate( + const { exchangeRate } = useExchangeRate( ExchangeRateType.LiquidToNative, - selectedProtocol.currency, + selectedProtocol.id, ); const { result: areAllDelegationsOccupiedOpt } = useDelegationsOccupiedStatus( @@ -83,38 +83,6 @@ const LiquidUnstakeCard: FC = () => { ? null : areAllDelegationsOccupiedOpt.unwrapOrDefault(); - const { result: minimumRedeemAmount } = useApiRx( - useCallback( - (api) => - api.query.lstMinting.minimumRedeem({ - Native: selectedProtocol.currency, - }), - [selectedProtocol.currency], - ), - TANGLE_RESTAKING_PARACHAIN_LOCAL_DEV_NETWORK.wsRpcEndpoint, - ); - - const { result: existentialDepositAmount } = useApi( - useCallback((api) => api.consts.balances.existentialDeposit, []), - TANGLE_RESTAKING_PARACHAIN_LOCAL_DEV_NETWORK.wsRpcEndpoint, - ); - - const minimumInputAmount = useMemo(() => { - if (minimumRedeemAmount === null || existentialDepositAmount === null) { - return null; - } - - return BN.max(minimumRedeemAmount, existentialDepositAmount); - }, [existentialDepositAmount, minimumRedeemAmount]); - - const maximumInputAmount = useMemo(() => { - if (liquidBalances === null) { - return null; - } - - return liquidBalances.get(selectedProtocol.token) ?? BN_ZERO; - }, [liquidBalances, selectedProtocol.token]); - const handleUnstakeClick = useCallback(() => { if (executeRedeemTx === null || fromAmount === null) { return; @@ -157,7 +125,7 @@ const LiquidUnstakeCard: FC = () => { protocolId={selectedProtocol.id} decimals={selectedProtocol.decimals} tooltip="Click to use all staked balance" - onClick={() => setFromAmount(maximumInputAmount)} + onClick={() => setFromAmount(maxSpendable)} /> ); @@ -174,8 +142,8 @@ const LiquidUnstakeCard: FC = () => { placeholder={`0 ${LST_PREFIX}${selectedProtocol.token}`} rightElement={stakedWalletBalance} isTokenLiquidVariant - minAmount={minimumInputAmount ?? undefined} - maxAmount={maximumInputAmount ?? undefined} + minAmount={minSpendable ?? undefined} + maxAmount={maxSpendable ?? undefined} maxErrorMessage="Not enough stake to redeem" onTokenClick={() => setIsSelectTokenModalOpen(true)} /> diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/useAgnosticLsBalance.ts b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/useAgnosticLsBalance.ts index b90d82d38..73e326963 100644 --- a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/useAgnosticLsBalance.ts +++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/useAgnosticLsBalance.ts @@ -6,13 +6,15 @@ import { erc20Abi } from 'viem'; import { EMPTY_VALUE_PLACEHOLDER } from '../../../constants'; import { LS_ERC20_TOKEN_MAP } from '../../../constants/liquidStaking/liquidStakingErc20'; import LIQUIFIER_TG_TOKEN_ABI from '../../../constants/liquidStaking/liquifierTgTokenAbi'; -import { LsProtocolId } from '../../../constants/liquidStaking/types'; +import { + getLsProtocolDef, + LsProtocolId, +} from '../../../constants/liquidStaking/types'; import useParachainBalances from '../../../data/liquidStaking/useParachainBalances'; import useContract from '../../../data/liquifier/useContract'; import useEvmAddress20 from '../../../hooks/useEvmAddress'; import useSubstrateAddress from '../../../hooks/useSubstrateAddress'; import isLsErc20TokenId from '../../../utils/liquidStaking/isLsErc20TokenId'; -import isLsParachainToken from '../../../utils/liquidStaking/isLsParachainToken'; const useAgnosticLsBalance = (isNative: boolean, protocolId: LsProtocolId) => { const substrateAddress = useSubstrateAddress(); @@ -63,9 +65,11 @@ const useAgnosticLsBalance = (isNative: boolean, protocolId: LsProtocolId) => { return; } - assert(isLsParachainToken(protocolId)); + const protocol = getLsProtocolDef(protocolId); - const newBalance = parachainBalances.get(protocolId) ?? BN_ZERO; + assert(protocol.type === 'parachain'); + + const newBalance = parachainBalances.get(protocol.token) ?? BN_ZERO; setBalance(newBalance); } diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/useLsSpendingLimits.ts b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/useLsSpendingLimits.ts new file mode 100644 index 000000000..bf8365562 --- /dev/null +++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/useLsSpendingLimits.ts @@ -0,0 +1,66 @@ +import { BN } from '@polkadot/util'; +import { useCallback, useMemo } from 'react'; + +import { LsProtocolId } from '../../../constants/liquidStaking/types'; +import useApi from '../../../hooks/useApi'; +import useApiRx from '../../../hooks/useApiRx'; +import useAgnosticLsBalance from './useAgnosticLsBalance'; +import { TANGLE_RESTAKING_PARACHAIN_LOCAL_DEV_NETWORK } from '../../../../../libs/webb-ui-components/src/constants/networks'; + +const useLsSpendingLimits = (isNative: boolean, protocolId: LsProtocolId) => { + const balance = useAgnosticLsBalance(isNative, protocolId); + + const { result: existentialDepositAmount } = useApi( + useCallback((api) => api.consts.balances.existentialDeposit, []), + TANGLE_RESTAKING_PARACHAIN_LOCAL_DEV_NETWORK.wsRpcEndpoint, + ); + + const { result: minimumMintingAmount } = useApiRx( + useCallback( + (api) => + api.query.lstMinting.minimumMint({ + Native: selectedProtocol.currency, + }), + [selectedProtocol.currency], + ), + TANGLE_RESTAKING_PARACHAIN_LOCAL_DEV_NETWORK.wsRpcEndpoint, + ); + + const { result: minimumRedeemAmount } = useApiRx( + useCallback( + (api) => + api.query.lstMinting.minimumRedeem({ + Native: selectedProtocol.currency, + }), + [selectedProtocol.currency], + ), + TANGLE_RESTAKING_PARACHAIN_LOCAL_DEV_NETWORK.wsRpcEndpoint, + ); + + const mintingOrRedeemingAmount = isNative + ? minimumMintingAmount + : minimumRedeemAmount; + + const minSpendable = useMemo(() => { + if ( + mintingOrRedeemingAmount === null || + existentialDepositAmount === null + ) { + return null; + } + + return BN.max(mintingOrRedeemingAmount, existentialDepositAmount); + }, [existentialDepositAmount, mintingOrRedeemingAmount]); + + const maxSpendable = (() => { + if (balance === null || typeof balance === 'string') { + return null; + } + + return balance; + })(); + + return { minSpendable, maxSpendable }; +}; + +export default useLsSpendingLimits; diff --git a/apps/tangle-dapp/constants/liquidStaking/types.ts b/apps/tangle-dapp/constants/liquidStaking/types.ts index cffc98103..facc381d1 100644 --- a/apps/tangle-dapp/constants/liquidStaking/types.ts +++ b/apps/tangle-dapp/constants/liquidStaking/types.ts @@ -49,6 +49,15 @@ export type LsErc20Token = | LsToken.LPT | LsToken.POL; +export const LS_PARACHAIN_TOKENS = [ + LsToken.DOT, + LsToken.GLMR, + LsToken.MANTA, + LsToken.ASTAR, + LsToken.PHALA, + LsToken.TNT, +] as const satisfies LsParachainToken[]; + export type LsParachainToken = Exclude; export const LS_ERC20_TOKEN_IDS = [ @@ -58,6 +67,12 @@ export const LS_ERC20_TOKEN_IDS = [ LsProtocolId.POLYGON, ] as const satisfies LsErc20TokenId[]; +export const LS_PARACHAIN_CHAIN_IDS = Object.values(LsProtocolId).filter( + (value): value is LsParachainChainId => + typeof value !== 'string' && + !LS_ERC20_TOKEN_IDS.includes(value as LsErc20TokenId), +) satisfies LsParachainChainId[]; + export type LsParachainChainDef = { type: 'parachain'; id: LsParachainChainId; diff --git a/apps/tangle-dapp/data/liquidStaking/useExchangeRate.ts b/apps/tangle-dapp/data/liquidStaking/useExchangeRate.ts index 9aa1b6b52..d8f37f15e 100644 --- a/apps/tangle-dapp/data/liquidStaking/useExchangeRate.ts +++ b/apps/tangle-dapp/data/liquidStaking/useExchangeRate.ts @@ -1,19 +1,25 @@ import { TANGLE_RESTAKING_PARACHAIN_LOCAL_DEV_NETWORK } from '@webb-tools/webb-ui-components/constants/networks'; -import { useMemo } from 'react'; +import { useCallback, useMemo } from 'react'; import { LsParachainCurrencyKey, ParachainCurrency, } from '../../constants/liquidStaking/liquidStakingParachain'; +import { + getLsProtocolDef, + LsErc20TokenDef, + LsProtocolId, +} from '../../constants/liquidStaking/types'; import useApiRx from '../../hooks/useApiRx'; import calculateBnRatio from '../../utils/calculateBnRatio'; +import usePolling from './usePolling'; export enum ExchangeRateType { NativeToLiquid, LiquidToNative, } -const useExchangeRate = ( +const useParachainExchangeRate = ( type: ExchangeRateType, currency: ParachainCurrency, ) => { @@ -55,4 +61,78 @@ const useExchangeRate = ( return exchangeRate; }; +// TODO: This NEEDS to be based on subscription for sure, since exchange rates are always changing. Perhaps make it return whether it is re-fetching, so that an effect can be shown on the UI to indicate that it is fetching the latest exchange rate, and also have it be ran in a 3 or 5 second interval. Will also need de-duping logic, error handling, and also prevent spamming requests when the parent component is re-rendered many times (e.g. by using a ref to store the latest fetch timestamp). Might want to extract this pattern into its own hook, similar to a subscription. Also consider having a global store (Zustand) for that custom hook that uses caching to prevent spamming requests when the same hook is used in multiple components, might need to accept a custom 'key' parameter to use as the cache key. +const useExchangeRate = (type: ExchangeRateType, protocolId: LsProtocolId) => { + const protocol = getLsProtocolDef(protocolId); + + const { result: tokenPoolAmount } = useApiRx((api) => { + if (protocol.type !== 'parachain') { + return null; + } + + const key: LsParachainCurrencyKey = + type === ExchangeRateType.NativeToLiquid + ? { Native: protocol.currency } + : { lst: protocol.currency }; + + return api.query.lstMinting.tokenPool(key); + }, TANGLE_RESTAKING_PARACHAIN_LOCAL_DEV_NETWORK.wsRpcEndpoint); + + const { result: lstTotalIssuance } = useApiRx((api) => { + if (protocol.type !== 'parachain') { + return null; + } + + return api.query.tokens.totalIssuance({ lst: protocol.currency }); + }, TANGLE_RESTAKING_PARACHAIN_LOCAL_DEV_NETWORK.wsRpcEndpoint); + + const parachainExchangeRate = useMemo(async () => { + // Not yet ready. + if (tokenPoolAmount === null || lstTotalIssuance === null) { + return null; + } + + const isEitherZero = tokenPoolAmount.isZero() || lstTotalIssuance.isZero(); + + // TODO: Need to review whether this is the right way to handle this edge case. + // Special case: No native tokens or liquidity available for conversion. + // Default to 1:1 exchange rate. This also helps prevent division by zero. + if (isEitherZero) { + return 1; + } + + const ratio = + type === ExchangeRateType.NativeToLiquid + ? calculateBnRatio(lstTotalIssuance, tokenPoolAmount) + : calculateBnRatio(tokenPoolAmount, lstTotalIssuance); + + return ratio; + }, [lstTotalIssuance, tokenPoolAmount, type]); + + const fetchErc20ExchangeRate = useCallback( + async (erc20TokenDef: LsErc20TokenDef) => { + // TODO: Implement. + + return 0; + }, + [], + ); + + const fetcher = useCallback(() => { + return protocol.type === 'parachain' + ? parachainExchangeRate + : fetchErc20ExchangeRate(protocol); + }, [fetchErc20ExchangeRate, parachainExchangeRate, protocol]); + + // TODO: Use polling for the ERC20 exchange rate, NOT the parachain exchange rate which is already based on a subscription. Might need a mechanism to 'pause' polling when the selected protocol is a parachain chain, that way it doesn't make unnecessary requests until an ERC20 token is selected. + const { value: exchangeRate, isRefreshing } = usePolling({ + fetcher, + // Refresh every 5 seconds. + refreshInterval: 5_000, + cacheKey: ['exchangeRate', protocolId], + }); + + return { exchangeRate, isRefreshing }; +}; + export default useExchangeRate; diff --git a/apps/tangle-dapp/data/liquidStaking/usePolling.ts b/apps/tangle-dapp/data/liquidStaking/usePolling.ts new file mode 100644 index 000000000..e50ccc119 --- /dev/null +++ b/apps/tangle-dapp/data/liquidStaking/usePolling.ts @@ -0,0 +1,35 @@ +import { useEffect, useState } from 'react'; + +export type UsePollingOptions = { + fetcher: () => Promise | T; + refreshInterval: number; + cacheKey?: unknown[]; +}; + +// TODO: Use Zustand global store for caching. + +const usePolling = ({ + fetcher, + // Default to a 3 second refresh interval. + refreshInterval = 3_000, + cacheKey, +}: UsePollingOptions) => { + const [value, setValue] = useState(null); + const [isRefreshing, setIsRefreshing] = useState(false); + + useEffect(() => { + const interval = setInterval(async () => { + setIsRefreshing(true); + setValue(await fetcher()); + setIsRefreshing(false); + }, refreshInterval); + + return () => { + clearInterval(interval); + }; + }, [fetcher, refreshInterval]); + + return { value, isRefreshing }; +}; + +export default usePolling; diff --git a/apps/tangle-dapp/utils/liquidStaking/isLsParachainChainId.ts b/apps/tangle-dapp/utils/liquidStaking/isLsParachainChainId.ts new file mode 100644 index 000000000..46bb6ebe7 --- /dev/null +++ b/apps/tangle-dapp/utils/liquidStaking/isLsParachainChainId.ts @@ -0,0 +1,13 @@ +import { + LS_PARACHAIN_CHAIN_IDS, + LsParachainChainId, + LsProtocolId, +} from '../../constants/liquidStaking/types'; + +function isLsParachainChainId( + protocolId: LsProtocolId, +): protocolId is LsParachainChainId { + return LS_PARACHAIN_CHAIN_IDS.includes(protocolId as LsParachainChainId); +} + +export default isLsParachainChainId; diff --git a/apps/tangle-dapp/utils/liquidStaking/isLsParachainToken.ts b/apps/tangle-dapp/utils/liquidStaking/isLsParachainToken.ts index df6686a3d..ead28b583 100644 --- a/apps/tangle-dapp/utils/liquidStaking/isLsParachainToken.ts +++ b/apps/tangle-dapp/utils/liquidStaking/isLsParachainToken.ts @@ -1,9 +1,13 @@ -import { LsProtocolId, LsToken } from '../../constants/liquidStaking/types'; +import { + LS_PARACHAIN_TOKENS, + LsParachainToken, + LsProtocolId, +} from '../../constants/liquidStaking/types'; function isLsParachainToken( tokenSymbol: string | LsProtocolId, -): tokenSymbol is LsToken { - return Object.values(LsToken).includes(tokenSymbol as LsToken); +): tokenSymbol is LsParachainToken { + return LS_PARACHAIN_TOKENS.includes(tokenSymbol as LsParachainToken); } export default isLsParachainToken; From d44f2e22b3c986ef320052785c087c2040b04fdd Mon Sep 17 00:00:00 2001 From: yurixander <101931215+yurixander@users.noreply.github.com> Date: Sun, 18 Aug 2024 19:24:51 -0400 Subject: [PATCH 16/60] feat(tangle-dapp): Create `useContractReadSubscription` hook --- .../data/liquidStaking/useExchangeRate.ts | 73 +++++++------------ .../data/liquidStaking/usePolling.ts | 20 ++++- .../tangle-dapp/data/liquifier/useContract.ts | 5 +- .../liquifier/useContractReadSubscription.ts | 40 ++++++++++ 4 files changed, 85 insertions(+), 53 deletions(-) create mode 100644 apps/tangle-dapp/data/liquifier/useContractReadSubscription.ts diff --git a/apps/tangle-dapp/data/liquidStaking/useExchangeRate.ts b/apps/tangle-dapp/data/liquidStaking/useExchangeRate.ts index d8f37f15e..808b487c1 100644 --- a/apps/tangle-dapp/data/liquidStaking/useExchangeRate.ts +++ b/apps/tangle-dapp/data/liquidStaking/useExchangeRate.ts @@ -1,10 +1,8 @@ import { TANGLE_RESTAKING_PARACHAIN_LOCAL_DEV_NETWORK } from '@webb-tools/webb-ui-components/constants/networks'; -import { useCallback, useMemo } from 'react'; +import { useCallback, useEffect, useMemo } from 'react'; +import { erc20Abi } from 'viem'; -import { - LsParachainCurrencyKey, - ParachainCurrency, -} from '../../constants/liquidStaking/liquidStakingParachain'; +import { LsParachainCurrencyKey } from '../../constants/liquidStaking/liquidStakingParachain'; import { getLsProtocolDef, LsErc20TokenDef, @@ -12,55 +10,14 @@ import { } from '../../constants/liquidStaking/types'; import useApiRx from '../../hooks/useApiRx'; import calculateBnRatio from '../../utils/calculateBnRatio'; -import usePolling from './usePolling'; +import useContractReadSubscription from '../liquifier/useContractReadSubscription'; +import usePolling, { PollingPrimaryCacheKey } from './usePolling'; export enum ExchangeRateType { NativeToLiquid, LiquidToNative, } -const useParachainExchangeRate = ( - type: ExchangeRateType, - currency: ParachainCurrency, -) => { - const { result: tokenPoolAmount } = useApiRx((api) => { - const key: LsParachainCurrencyKey = - type === ExchangeRateType.NativeToLiquid - ? { Native: currency } - : { lst: currency }; - - return api.query.lstMinting.tokenPool(key); - }, TANGLE_RESTAKING_PARACHAIN_LOCAL_DEV_NETWORK.wsRpcEndpoint); - - const { result: lstTotalIssuance } = useApiRx((api) => { - return api.query.tokens.totalIssuance({ lst: currency }); - }, TANGLE_RESTAKING_PARACHAIN_LOCAL_DEV_NETWORK.wsRpcEndpoint); - - const exchangeRate = useMemo(() => { - if (tokenPoolAmount === null || lstTotalIssuance === null) { - return null; - } - - const isEitherZero = tokenPoolAmount.isZero() || lstTotalIssuance.isZero(); - - // TODO: Need to review whether this is the right way to handle this edge case. - // Special case: No native tokens or liquidity available for conversion. - // Default to 1:1 exchange rate. This also helps prevent division by zero. - if (isEitherZero) { - return 1; - } - - const ratio = - type === ExchangeRateType.NativeToLiquid - ? calculateBnRatio(lstTotalIssuance, tokenPoolAmount) - : calculateBnRatio(tokenPoolAmount, lstTotalIssuance); - - return ratio; - }, [lstTotalIssuance, tokenPoolAmount, type]); - - return exchangeRate; -}; - // TODO: This NEEDS to be based on subscription for sure, since exchange rates are always changing. Perhaps make it return whether it is re-fetching, so that an effect can be shown on the UI to indicate that it is fetching the latest exchange rate, and also have it be ran in a 3 or 5 second interval. Will also need de-duping logic, error handling, and also prevent spamming requests when the parent component is re-rendered many times (e.g. by using a ref to store the latest fetch timestamp). Might want to extract this pattern into its own hook, similar to a subscription. Also consider having a global store (Zustand) for that custom hook that uses caching to prevent spamming requests when the same hook is used in multiple components, might need to accept a custom 'key' parameter to use as the cache key. const useExchangeRate = (type: ExchangeRateType, protocolId: LsProtocolId) => { const protocol = getLsProtocolDef(protocolId); @@ -124,11 +81,31 @@ const useExchangeRate = (type: ExchangeRateType, protocolId: LsProtocolId) => { : fetchErc20ExchangeRate(protocol); }, [fetchErc20ExchangeRate, parachainExchangeRate, protocol]); + // TODO: Will need one for the LST total issuance, and another for the token pool amount. + const { + value: erc20TotalIssuance, + setIsPaused: setIsErc20TotalIssuancePaused, + } = useContractReadSubscription(erc20Abi, { + address: '0x', + functionName: 'totalSupply', + args: [], + }); + + // Pause or resume ERC20-based exchange rate fetching based + // on whether the requested protocol is a parachain or an ERC20 token. + // This helps prevent unnecessary requests. + useEffect(() => { + const isPaused = protocol.type === 'parachain'; + + setIsErc20TotalIssuancePaused(isPaused); + }, []); + // TODO: Use polling for the ERC20 exchange rate, NOT the parachain exchange rate which is already based on a subscription. Might need a mechanism to 'pause' polling when the selected protocol is a parachain chain, that way it doesn't make unnecessary requests until an ERC20 token is selected. const { value: exchangeRate, isRefreshing } = usePolling({ fetcher, // Refresh every 5 seconds. refreshInterval: 5_000, + primaryCacheKey: PollingPrimaryCacheKey.EXCHANGE_RATE, cacheKey: ['exchangeRate', protocolId], }); diff --git a/apps/tangle-dapp/data/liquidStaking/usePolling.ts b/apps/tangle-dapp/data/liquidStaking/usePolling.ts index e50ccc119..31961e339 100644 --- a/apps/tangle-dapp/data/liquidStaking/usePolling.ts +++ b/apps/tangle-dapp/data/liquidStaking/usePolling.ts @@ -1,8 +1,14 @@ import { useEffect, useState } from 'react'; -export type UsePollingOptions = { - fetcher: () => Promise | T; - refreshInterval: number; +export enum PollingPrimaryCacheKey { + EXCHANGE_RATE, + CONTRACT_READ_SUBSCRIPTION, +} + +export type PollingOptions = { + fetcher: (() => Promise | T) | null; + refreshInterval?: number; + primaryCacheKey: PollingPrimaryCacheKey; cacheKey?: unknown[]; }; @@ -12,13 +18,19 @@ const usePolling = ({ fetcher, // Default to a 3 second refresh interval. refreshInterval = 3_000, + primaryCacheKey, cacheKey, -}: UsePollingOptions) => { +}: PollingOptions) => { const [value, setValue] = useState(null); const [isRefreshing, setIsRefreshing] = useState(false); useEffect(() => { const interval = setInterval(async () => { + // Fetcher isn't ready to be called yet. + if (fetcher === null) { + return; + } + setIsRefreshing(true); setValue(await fetcher()); setIsRefreshing(false); diff --git a/apps/tangle-dapp/data/liquifier/useContract.ts b/apps/tangle-dapp/data/liquifier/useContract.ts index 1de621de0..d95f1a1a9 100644 --- a/apps/tangle-dapp/data/liquifier/useContract.ts +++ b/apps/tangle-dapp/data/liquifier/useContract.ts @@ -13,6 +13,7 @@ import { } from 'viem/actions'; import { mainnet } from 'viem/chains'; import { useConnectorClient } from 'wagmi'; +import { ReadContractReturnType } from 'wagmi/actions'; import useEvmAddress20 from '../../hooks/useEvmAddress'; import useEthereumMainnetClient from './useEthereumMainnetClient'; @@ -43,7 +44,9 @@ const useContract = (abi: Abi) => { const read = useCallback( >( options: ContractReadOptions, - ) => { + ): Promise< + ReadContractReturnType + > => { assert( publicClient !== null, "Should not be able to call this function if the client isn't ready yet", diff --git a/apps/tangle-dapp/data/liquifier/useContractReadSubscription.ts b/apps/tangle-dapp/data/liquifier/useContractReadSubscription.ts new file mode 100644 index 000000000..b721dcc05 --- /dev/null +++ b/apps/tangle-dapp/data/liquifier/useContractReadSubscription.ts @@ -0,0 +1,40 @@ +import { useCallback, useState } from 'react'; +import { Abi as ViemAbi, ContractFunctionName } from 'viem'; + +import usePolling, { + PollingPrimaryCacheKey, +} from '../liquidStaking/usePolling'; +import useContract, { ContractReadOptions } from './useContract'; +import { BN } from '@polkadot/util'; + +const useContractReadSubscription = < + Abi extends ViemAbi, + FunctionName extends ContractFunctionName, +>( + abi: Abi, + options: ContractReadOptions, +) => { + const [isPaused, setIsPaused] = useState(false); + const { read } = useContract(abi); + + const fetcher = useCallback(async () => { + if (isPaused || read === null) { + return null; + } + + return read(options); + }, [isPaused, options, read]); + + const { value, isRefreshing } = usePolling({ + // By providing null, it signals to the hook to maintain + // its current value and not refresh. + fetcher: isPaused ? null : fetcher, + primaryCacheKey: PollingPrimaryCacheKey.CONTRACT_READ_SUBSCRIPTION, + // TODO: Options include args, which may be objects. Having these objects be stable across renders is important for the intended caching behavior. Instead of using the options object directly, consider providing the user a way to specify a cache key. + cacheKey: Object.values(options), + }); + + return { value, isRefreshing, isPaused, setIsPaused }; +}; + +export default useContractReadSubscription; From c5204f97e977f8789d95fac75654d8a21590fae8 Mon Sep 17 00:00:00 2001 From: yurixander <101931215+yurixander@users.noreply.github.com> Date: Tue, 20 Aug 2024 01:47:44 -0400 Subject: [PATCH 17/60] fix(tangle-dapp): Merge leftovers --- apps/tangle-dapp/app/liquid-staking/page.tsx | 1 - .../LiquidStaking/stakeAndUnstake/LiquidStakeCard.tsx | 2 +- .../LiquidStaking/stakeAndUnstake/LiquidStakingInput.tsx | 2 -- .../LiquidStaking/stakeAndUnstake/LiquidStakingTokenItem.tsx | 3 --- .../LiquidStaking/stakeAndUnstake/LiquidUnstakeCard.tsx | 2 +- .../LiquidStaking/stakeAndUnstake/SelectTokenModal.tsx | 2 -- 6 files changed, 2 insertions(+), 10 deletions(-) diff --git a/apps/tangle-dapp/app/liquid-staking/page.tsx b/apps/tangle-dapp/app/liquid-staking/page.tsx index c164ebf65..89894cbcb 100644 --- a/apps/tangle-dapp/app/liquid-staking/page.tsx +++ b/apps/tangle-dapp/app/liquid-staking/page.tsx @@ -38,7 +38,6 @@ const LiquidStakingPage: FC = () => { return ( { {/* Details */}
            diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakingInput.tsx b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakingInput.tsx index 04ed85c27..a33c5587e 100644 --- a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakingInput.tsx +++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakingInput.tsx @@ -2,7 +2,6 @@ import { BN } from '@polkadot/util'; import { DropdownMenuTrigger } from '@radix-ui/react-dropdown-menu'; -import { ChainIcon } from '@webb-tools/icons'; import { Dropdown, DropdownBody, @@ -21,7 +20,6 @@ import { } from '../../../constants/liquidStaking/types'; import { ERROR_NOT_ENOUGH_BALANCE } from '../../../containers/ManageProfileModalContainer/Independent/IndependentAllocationInput'; import useInputAmount from '../../../hooks/useInputAmount'; -import { LiquidStakingToken } from '../../../types/liquidStaking'; import formatBn from '../../../utils/formatBn'; import DropdownChevronIcon from './DropdownChevronIcon'; import ProtocolLogo from './ProtocolLogo'; diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakingTokenItem.tsx b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakingTokenItem.tsx index 441693bcc..84ab7ed94 100644 --- a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakingTokenItem.tsx +++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakingTokenItem.tsx @@ -9,7 +9,6 @@ import { FC, useMemo } from 'react'; import LSTTokenIcon from '../../../components/LSTTokenIcon'; import { StaticAssetPath } from '../../../constants'; import { - LsProtocolId, LST_PREFIX, LsToken, TVS_TOOLTIP, @@ -19,7 +18,6 @@ import formatTangleBalance from '../../../utils/formatTangleBalance'; import StatItem from '../StatItem'; export type LiquidStakingTokenItemProps = { - protocolId: LsProtocolId; title: string; tokenSymbol: LsToken; totalValueStaked: number; @@ -28,7 +26,6 @@ export type LiquidStakingTokenItemProps = { const LiquidStakingTokenItem: FC = ({ title, - protocolId, tokenSymbol, totalValueStaked, totalStaked, diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidUnstakeCard.tsx b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidUnstakeCard.tsx index d7997346e..088852a55 100644 --- a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidUnstakeCard.tsx +++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidUnstakeCard.tsx @@ -164,9 +164,9 @@ const LiquidUnstakeCard: FC = () => { {/* Details */}
            Date: Tue, 20 Aug 2024 17:32:12 -0400 Subject: [PATCH 18/60] fix(tangle-dapp): Solve most errors, create `CrossChainTime` class --- .../LiquidStakingSelectionTable.tsx | 2 +- .../stakeAndUnstake/LiquidStakeCard.tsx | 46 +++++++++------ .../stakeAndUnstake/LiquidStakingInput.tsx | 15 ++--- .../stakeAndUnstake/LiquidUnstakeCard.tsx | 27 +-------- .../UnstakePeriodDetailItem.tsx | 56 ++++++++++++++----- .../stakeAndUnstake/useLsSpendingLimits.ts | 41 ++++++++++---- .../liquidStaking/liquidStakingErc20.ts | 9 +++ .../constants/liquidStaking/types.ts | 3 + .../liquidStaking/useLiquidStakingItems.ts | 17 +++--- .../liquidStaking/useLiquidStakingStore.ts | 9 +-- apps/tangle-dapp/utils/CrossChainTime.ts | 51 +++++++++++++++++ 11 files changed, 191 insertions(+), 85 deletions(-) create mode 100644 apps/tangle-dapp/utils/CrossChainTime.ts diff --git a/apps/tangle-dapp/components/LiquidStaking/LiquidStakingSelectionTable.tsx b/apps/tangle-dapp/components/LiquidStaking/LiquidStakingSelectionTable.tsx index a635925dc..2560cc13a 100644 --- a/apps/tangle-dapp/components/LiquidStaking/LiquidStakingSelectionTable.tsx +++ b/apps/tangle-dapp/components/LiquidStaking/LiquidStakingSelectionTable.tsx @@ -44,7 +44,7 @@ const SELECTED_ITEMS_COLUMN_SORT = { export const LiquidStakingSelectionTable = () => { const selectedChainId = useLiquidStakingStore( - (state) => state.selectedChainId, + (state) => state.selectedProtocolId, ); const setSelectedItems = useLiquidStakingStore( (state) => state.setSelectedItems, diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakeCard.tsx b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakeCard.tsx index 50cf7a5a2..39f213e47 100644 --- a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakeCard.tsx +++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakeCard.tsx @@ -27,6 +27,7 @@ import useExchangeRate, { } from '../../../data/liquidStaking/useExchangeRate'; import { useLiquidStakingStore } from '../../../data/liquidStaking/useLiquidStakingStore'; import useMintTx from '../../../data/liquidStaking/useMintTx'; +import useLiquifierDeposit from '../../../data/liquifier/useLiquifierDeposit'; import useSearchParamState from '../../../hooks/useSearchParamState'; import useSearchParamSync from '../../../hooks/useSearchParamSync'; import { TxStatus } from '../../../hooks/useSubstrateTx'; @@ -46,39 +47,48 @@ const LiquidStakeCard: FC = () => { stringify: (value) => value?.toString(), }); - const { selectedChainId, setSelectedChainId } = useLiquidStakingStore(); + const { selectedProtocolId, setSelectedProtocolId } = useLiquidStakingStore(); const { execute: executeMintTx, status: mintTxStatus } = useMintTx(); + const performLiquifierDeposit = useLiquifierDeposit(); const { maxSpendable, minSpendable } = useLsSpendingLimits( true, - selectedChainId, + selectedProtocolId, ); - const selectedProtocol = getLsProtocolDef(selectedChainId); + const selectedProtocol = getLsProtocolDef(selectedProtocolId); useSearchParamSync({ key: LsSearchParamKey.PROTOCOL_ID, - value: selectedChainId, + value: selectedProtocolId, parse: (value) => z.nativeEnum(LsProtocolId).parse(parseInt(value)), stringify: (value) => value.toString(), - setValue: setSelectedChainId, + setValue: setSelectedProtocolId, }); const { exchangeRate } = useExchangeRate( ExchangeRateType.NativeToLiquid, - selectedProtocol.currency, + selectedProtocolId, ); - const handleStakeClick = useCallback(() => { - if (executeMintTx === null || fromAmount === null) { + const handleStakeClick = useCallback(async () => { + // Not ready yet; no amount given. + if (fromAmount === null) { return; } - executeMintTx({ - amount: fromAmount, - currency: selectedProtocol.currency, - }); - }, [executeMintTx, fromAmount, selectedProtocol.currency]); + if (selectedProtocol.type === 'parachain' && executeMintTx !== null) { + executeMintTx({ + amount: fromAmount, + currency: selectedProtocol.currency, + }); + } else if ( + selectedProtocol.type === 'erc20' && + performLiquifierDeposit !== null + ) { + await performLiquifierDeposit(selectedProtocol.id, fromAmount); + } + }, [executeMintTx, fromAmount, performLiquifierDeposit, selectedProtocol]); const toAmount = useMemo(() => { if (fromAmount === null || exchangeRate === null) { @@ -90,7 +100,7 @@ const LiquidStakeCard: FC = () => { const walletBalance = ( setFromAmount(maxSpendable)} @@ -101,14 +111,14 @@ const LiquidStakeCard: FC = () => { <> @@ -130,7 +140,7 @@ const LiquidStakeCard: FC = () => { {/* Details */}
            @@ -141,7 +151,7 @@ const LiquidStakeCard: FC = () => { token={selectedProtocol.token} /> - +
            @@ -199,7 +199,7 @@ const ProtocolSelector: FC = ({ onSelect={() => setProtocolId(protocolId)} className="px-3 normal-case" > - {protocol.networkName} + {protocol.name} ); diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/UnstakePeriodDetailItem.tsx b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/UnstakePeriodDetailItem.tsx index b4753bb8a..b1ca2efb3 100644 --- a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/UnstakePeriodDetailItem.tsx +++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/UnstakePeriodDetailItem.tsx @@ -10,12 +10,18 @@ export type UnstakePeriodDetailItemProps = { protocolId: LsProtocolId; }; +type UnstakePeriod = { + value: number; + unit: string; + isEstimate: boolean; +}; + const UnstakePeriodDetailItem: FC = ({ protocolId, }) => { const protocol = getLsProtocolDef(protocolId); - const unlockPeriod = ((): [number, string] | null => { + const unlockPeriod = ((): UnstakePeriod | null => { const unlockPeriod = new CrossChainTime( protocol.timeUnit, protocol.unstakingPeriod, @@ -25,8 +31,13 @@ const UnstakePeriodDetailItem: FC = ({ // TODO: Special case for 0 days? const plurality = days > 1 ? 'days' : 'day'; + const roundedDays = Math.round(days); - return [days, plurality]; + return { + unit: plurality, + value: roundedDays, + isEstimate: days !== roundedDays, + }; })(); const value = @@ -35,7 +46,8 @@ const UnstakePeriodDetailItem: FC = ({ ) : (
            - {unlockPeriod[0].toString()} {unlockPeriod[1]} + {unlockPeriod.isEstimate && '~'} + {unlockPeriod.value.toString()} {unlockPeriod.unit}
            ); diff --git a/apps/tangle-dapp/constants/liquidStaking/constants.ts b/apps/tangle-dapp/constants/liquidStaking/constants.ts index 381baf5bc..37a4358fe 100644 --- a/apps/tangle-dapp/constants/liquidStaking/constants.ts +++ b/apps/tangle-dapp/constants/liquidStaking/constants.ts @@ -18,7 +18,6 @@ const CHAINLINK: LsErc20TokenDef = { type: 'erc20', id: LsProtocolId.CHAINLINK, name: 'Chainlink', - networkName: 'Ethereum Mainnet', // TODO: Add logo and link it here. logo: StaticAssetPath.LIQUID_STAKING_TOKEN_PHALA, token: LsToken.LINK, @@ -36,7 +35,6 @@ const THE_GRAPH: LsErc20TokenDef = { type: 'erc20', id: LsProtocolId.THE_GRAPH, name: 'The Graph', - networkName: 'Ethereum Mainnet', // TODO: Add logo and link it here. logo: StaticAssetPath.LIQUID_STAKING_TOKEN_PHALA, token: LsToken.GRT, @@ -54,7 +52,6 @@ const LIVEPEER: LsErc20TokenDef = { type: 'erc20', id: LsProtocolId.LIVEPEER, name: 'Livepeer', - networkName: 'Ethereum Mainnet', // TODO: Add logo and link it here. logo: StaticAssetPath.LIQUID_STAKING_TOKEN_PHALA, token: LsToken.LPT, @@ -72,7 +69,6 @@ const POLYGON: LsErc20TokenDef = { type: 'erc20', id: LsProtocolId.POLYGON, name: 'Polygon', - networkName: 'Ethereum Mainnet', // TODO: Add logo and link it here. logo: StaticAssetPath.LIQUID_STAKING_TOKEN_PHALA, token: LsToken.POL, @@ -92,7 +88,6 @@ const POLKADOT: LsParachainChainDef = { name: 'Polkadot', token: LsToken.DOT, logo: StaticAssetPath.LIQUID_STAKING_TOKEN_POLKADOT, - networkName: 'Polkadot Mainnet', currency: 'Dot', decimals: 10, rpcEndpoint: 'wss://polkadot-rpc.dwellir.com', @@ -106,7 +101,6 @@ const PHALA: LsParachainChainDef = { name: 'Phala', token: LsToken.PHALA, logo: StaticAssetPath.LIQUID_STAKING_TOKEN_PHALA, - networkName: 'Phala', currency: 'Pha', decimals: 18, rpcEndpoint: 'wss://api.phala.network/ws', @@ -120,7 +114,6 @@ const MOONBEAM: LsParachainChainDef = { name: 'Moonbeam', token: LsToken.GLMR, logo: StaticAssetPath.LIQUID_STAKING_TOKEN_GLIMMER, - networkName: 'Moonbeam', // TODO: No currency entry for GLMR in the Tangle Primitives? currency: 'Dot', decimals: 18, @@ -135,7 +128,6 @@ const ASTAR: LsParachainChainDef = { name: 'Astar', token: LsToken.ASTAR, logo: StaticAssetPath.LIQUID_STAKING_TOKEN_ASTAR, - networkName: 'Astar', // TODO: No currency entry for ASTAR in the Tangle Primitives? currency: 'Dot', decimals: 18, @@ -150,7 +142,6 @@ const MANTA: LsParachainChainDef = { name: 'Manta', token: LsToken.MANTA, logo: StaticAssetPath.LIQUID_STAKING_TOKEN_MANTA, - networkName: 'Manta', // TODO: No currency entry for ASTAR in the Tangle Primitives? currency: 'Dot', decimals: 18, @@ -165,7 +156,6 @@ const TANGLE_RESTAKING_PARACHAIN: LsParachainChainDef = { name: 'Tangle Parachain', token: LsToken.TNT, logo: StaticAssetPath.LIQUID_STAKING_TANGLE_LOGO, - networkName: 'Tangle Parachain', currency: 'Bnc', decimals: TANGLE_TOKEN_DECIMALS, rpcEndpoint: TANGLE_RESTAKING_PARACHAIN_LOCAL_DEV_NETWORK.wsRpcEndpoint, diff --git a/apps/tangle-dapp/constants/liquidStaking/types.ts b/apps/tangle-dapp/constants/liquidStaking/types.ts index 30bf2c0e6..fa863140e 100644 --- a/apps/tangle-dapp/constants/liquidStaking/types.ts +++ b/apps/tangle-dapp/constants/liquidStaking/types.ts @@ -53,7 +53,6 @@ export type LsParachainToken = Exclude; type ProtocolDefCommon = { name: string; decimals: number; - networkName: string; timeUnit: CrossChainTimeUnit; unstakingPeriod: number; logo: StaticAssetPath; @@ -71,7 +70,6 @@ export interface LsParachainChainDef extends ProtocolDefCommon { export interface LsErc20TokenDef extends ProtocolDefCommon { type: 'erc20'; id: LsErc20TokenId; - networkName: string; token: LsErc20Token; address: HexString; liquifierAdapterAddress: HexString; From 1942688cce5aabfd4e77d9b4f32901fcac014865 Mon Sep 17 00:00:00 2001 From: yurixander <101931215+yurixander@users.noreply.github.com> Date: Wed, 21 Aug 2024 00:13:39 -0400 Subject: [PATCH 25/60] feat(tangle-dapp): Change network name when appropriate --- .../LiquidStaking/stakeAndUnstake/LiquidUnstakeCard.tsx | 1 + .../NetworkSelector/NetworkSelectionButton.tsx | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidUnstakeCard.tsx b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidUnstakeCard.tsx index ba97f53c3..2d02b8a83 100644 --- a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidUnstakeCard.tsx +++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidUnstakeCard.tsx @@ -39,6 +39,7 @@ const LiquidUnstakeCard: FC = () => { const [isRequestSubmittedModalOpen, setIsRequestSubmittedModalOpen] = useState(false); + // TODO: Need to use the global store in order for the network switcher to pick up EVM vs. Parachain chains. const [selectedChainId, setSelectedChainId] = useSearchParamState({ key: LsSearchParamKey.PROTOCOL_ID, diff --git a/apps/tangle-dapp/components/NetworkSelector/NetworkSelectionButton.tsx b/apps/tangle-dapp/components/NetworkSelector/NetworkSelectionButton.tsx index 7d88dd9f7..ed0064c99 100644 --- a/apps/tangle-dapp/components/NetworkSelector/NetworkSelectionButton.tsx +++ b/apps/tangle-dapp/components/NetworkSelector/NetworkSelectionButton.tsx @@ -22,9 +22,11 @@ import { type FC, useCallback, useMemo } from 'react'; import { twMerge } from 'tailwind-merge'; import useNetworkStore from '../../context/useNetworkStore'; +import { useLiquidStakingStore } from '../../data/liquidStaking/useLiquidStakingStore'; import useNetworkSwitcher from '../../hooks/useNetworkSwitcher'; import { PagePath } from '../../types'; import createCustomNetwork from '../../utils/createCustomNetwork'; +import isLsErc20TokenId from '../../utils/liquidStaking/isLsErc20TokenId'; import { NetworkSelectorDropdown } from './NetworkSelectorDropdown'; // TODO: Currently hard-coded, but shouldn't it always be the Tangle icon, since it's not switching chains but rather networks within Tangle? If so, find some constant somewhere instead of having it hard-coded here. @@ -37,6 +39,7 @@ const NetworkSelectionButton: FC = () => { const { network } = useNetworkStore(); const { switchNetwork, isCustom } = useNetworkSwitcher(); const pathname = usePathname(); + const { selectedProtocolId } = useLiquidStakingStore(); // TODO: Handle switching network on EVM wallet here. const switchToCustomNetwork = useCallback( @@ -95,13 +98,17 @@ const NetworkSelectionButton: FC = () => { // Network can't be switched from the Tangle Restaking Parachain while // on liquid staking page. else if (isInLiquidStakingPath) { + const liquidStakingNetworkName = isLsErc20TokenId(selectedProtocolId) + ? 'Ethereum Mainnet' + : TANGLE_RESTAKING_PARACHAIN_LOCAL_DEV_NETWORK.name; + return ( From b52311b3bd996f8b8c977ef23574268a7607b7c3 Mon Sep 17 00:00:00 2001 From: yurixander <101931215+yurixander@users.noreply.github.com> Date: Wed, 21 Aug 2024 00:17:01 -0400 Subject: [PATCH 26/60] feat(tangle-dapp): Also change chain icon --- .../NetworkSelector/NetworkSelectionButton.tsx | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/apps/tangle-dapp/components/NetworkSelector/NetworkSelectionButton.tsx b/apps/tangle-dapp/components/NetworkSelector/NetworkSelectionButton.tsx index ed0064c99..308d4c4fb 100644 --- a/apps/tangle-dapp/components/NetworkSelector/NetworkSelectionButton.tsx +++ b/apps/tangle-dapp/components/NetworkSelector/NetworkSelectionButton.tsx @@ -102,6 +102,10 @@ const NetworkSelectionButton: FC = () => { ? 'Ethereum Mainnet' : TANGLE_RESTAKING_PARACHAIN_LOCAL_DEV_NETWORK.name; + const chainIconName = isLsErc20TokenId(selectedProtocolId) + ? 'ethereum' + : TANGLE_TESTNET_CHAIN_NAME; + return ( @@ -109,6 +113,7 @@ const NetworkSelectionButton: FC = () => { @@ -167,12 +172,14 @@ type TriggerButtonProps = { className?: string; networkName: string; isLoading?: boolean; + chainIconName?: string; }; const TriggerButton: FC = ({ - isLoading, + isLoading = false, networkName, className, + chainIconName = TANGLE_TESTNET_CHAIN_NAME, }) => { return ( = ({ {isLoading ? ( ) : ( - + )}
            From fc05542a0424eb19648a6b0a34570e146fa11b82 Mon Sep 17 00:00:00 2001 From: yurixander <101931215+yurixander@users.noreply.github.com> Date: Wed, 21 Aug 2024 03:26:48 -0400 Subject: [PATCH 27/60] feat(tangle-dapp): Use Sepolia dummy smart contracts in development mode --- .../stakeAndUnstake/LiquidUnstakeCard.tsx | 14 +++-- .../NetworkSelectionButton.tsx | 10 ++- apps/tangle-dapp/constants/index.ts | 5 +- .../constants/liquidStaking/constants.ts | 61 ++++++++++++++----- .../tangle-dapp/data/liquifier/useContract.ts | 11 +++- .../data/liquifier/useLiquifierDeposit.ts | 4 +- .../data/liquifier/useLiquifierUnlock.ts | 61 +++++++++++++++++++ ...ent.ts => useViemPublicClientWithChain.ts} | 11 ++-- apps/tangle-dapp/hooks/useTxNotification.tsx | 5 +- 9 files changed, 145 insertions(+), 37 deletions(-) create mode 100644 apps/tangle-dapp/data/liquifier/useLiquifierUnlock.ts rename apps/tangle-dapp/data/liquifier/{useEthereumMainnetClient.ts => useViemPublicClientWithChain.ts} (57%) diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidUnstakeCard.tsx b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidUnstakeCard.tsx index 2d02b8a83..007735bc0 100644 --- a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidUnstakeCard.tsx +++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidUnstakeCard.tsx @@ -19,6 +19,7 @@ import useExchangeRate, { ExchangeRateType, } from '../../../data/liquidStaking/useExchangeRate'; import useRedeemTx from '../../../data/liquidStaking/useRedeemTx'; +import useLiquifierUnlock from '../../../data/liquifier/useLiquifierUnlock'; import useSearchParamState from '../../../hooks/useSearchParamState'; import useSearchParamSync from '../../../hooks/useSearchParamSync'; import { TxStatus } from '../../../hooks/useSubstrateTx'; @@ -54,6 +55,8 @@ const LiquidUnstakeCard: FC = () => { txHash: redeemTxHash, } = useRedeemTx(); + const performLiquifierUnlock = useLiquifierUnlock(); + const { minSpendable, maxSpendable } = useLsSpendingLimits( false, selectedChainId, @@ -74,7 +77,7 @@ const LiquidUnstakeCard: FC = () => { stringify: (value) => value?.toString(), }); - const handleUnstakeClick = useCallback(() => { + const handleUnstakeClick = useCallback(async () => { // Cannot perform transaction: Amount not set. if (fromAmount === null) { return; @@ -85,10 +88,13 @@ const LiquidUnstakeCard: FC = () => { amount: fromAmount, currency: selectedProtocol.currency, }); + } else if ( + selectedProtocol.type === 'erc20' && + performLiquifierUnlock !== null + ) { + await performLiquifierUnlock(selectedProtocol.id, fromAmount); } - - // TODO: Perform action for EVM-based chains. - }, [executeRedeemTx, fromAmount, selectedProtocol]); + }, [executeRedeemTx, fromAmount, performLiquifierUnlock, selectedProtocol]); const toAmount = useMemo(() => { if (fromAmount === null || exchangeRate === null) { diff --git a/apps/tangle-dapp/components/NetworkSelector/NetworkSelectionButton.tsx b/apps/tangle-dapp/components/NetworkSelector/NetworkSelectionButton.tsx index 308d4c4fb..fbb07992e 100644 --- a/apps/tangle-dapp/components/NetworkSelector/NetworkSelectionButton.tsx +++ b/apps/tangle-dapp/components/NetworkSelector/NetworkSelectionButton.tsx @@ -21,6 +21,7 @@ import { usePathname } from 'next/navigation'; import { type FC, useCallback, useMemo } from 'react'; import { twMerge } from 'tailwind-merge'; +import { IS_PRODUCTION_ENV } from '../../constants/env'; import useNetworkStore from '../../context/useNetworkStore'; import { useLiquidStakingStore } from '../../data/liquidStaking/useLiquidStakingStore'; import useNetworkSwitcher from '../../hooks/useNetworkSwitcher'; @@ -99,7 +100,9 @@ const NetworkSelectionButton: FC = () => { // on liquid staking page. else if (isInLiquidStakingPath) { const liquidStakingNetworkName = isLsErc20TokenId(selectedProtocolId) - ? 'Ethereum Mainnet' + ? IS_PRODUCTION_ENV + ? 'Ethereum Mainnet' + : 'Sepolia Testnet' : TANGLE_RESTAKING_PARACHAIN_LOCAL_DEV_NETWORK.name; const chainIconName = isLsErc20TokenId(selectedProtocolId) @@ -114,6 +117,7 @@ const NetworkSelectionButton: FC = () => { className="opacity-60 cursor-not-allowed hover:!bg-none dark:hover:!bg-none" networkName={liquidStakingNetworkName} chainIconName={chainIconName} + isLocked /> @@ -173,6 +177,7 @@ type TriggerButtonProps = { networkName: string; isLoading?: boolean; chainIconName?: string; + isLocked?: boolean; }; const TriggerButton: FC = ({ @@ -180,6 +185,7 @@ const TriggerButton: FC = ({ networkName, className, chainIconName = TANGLE_TESTNET_CHAIN_NAME, + isLocked = false, }) => { return ( = ({ {networkName} - + {!isLocked && }
            ); diff --git a/apps/tangle-dapp/constants/index.ts b/apps/tangle-dapp/constants/index.ts index 21883c840..395f99c60 100644 --- a/apps/tangle-dapp/constants/index.ts +++ b/apps/tangle-dapp/constants/index.ts @@ -63,8 +63,9 @@ export enum TxName { LST_REDEEM = 'redeem', LST_REBOND = 'cancel unstake request', LST_WITHDRAW_REDEEM = 'withdraw redeemed tokens', - LST_LIQUIFIER_APPROVE = 'liquifier approve spending', - LST_LIQUIFIER_DEPOSIT = 'liquifier deposit', + LS_LIQUIFIER_APPROVE = 'liquifier approve spending', + LS_LIQUIFIER_DEPOSIT = 'liquifier deposit', + LS_LIQUIFIER_UNLOCK = 'liquifier unlock', } export const PAYMENT_DESTINATION_OPTIONS: StakingRewardsDestinationDisplayText[] = diff --git a/apps/tangle-dapp/constants/liquidStaking/constants.ts b/apps/tangle-dapp/constants/liquidStaking/constants.ts index 37a4358fe..de5ae30e1 100644 --- a/apps/tangle-dapp/constants/liquidStaking/constants.ts +++ b/apps/tangle-dapp/constants/liquidStaking/constants.ts @@ -1,8 +1,10 @@ +import { HexString } from '@polkadot/util/types'; import { TANGLE_TOKEN_DECIMALS } from '@webb-tools/dapp-config'; import { TANGLE_RESTAKING_PARACHAIN_LOCAL_DEV_NETWORK } from '@webb-tools/webb-ui-components/constants/networks'; import { CrossChainTimeUnit } from '../../utils/CrossChainTime'; import { StaticAssetPath } from '..'; +import { IS_PRODUCTION_ENV } from '../env'; import { LsErc20TokenDef, LsErc20TokenId, @@ -14,6 +16,13 @@ import { LsToken, } from './types'; +const SEPOLIA_LIQUIFIER_CONTRACT_ADDRESS: HexString = + '0xCE0148DbA55c9719B59642f5cBf4F26e67F44E70'; + +// TODO: Fill in the actual addresses once deployed to a testnet. +const SEPOLIA_ERC20_CONTRACT_ADDRESS: HexString = '0x'; +const SEPOLIA_TG_TOKEN_CONTRACT_ADDRESS: HexString = '0x'; + const CHAINLINK: LsErc20TokenDef = { type: 'erc20', id: LsProtocolId.CHAINLINK, @@ -22,11 +31,16 @@ const CHAINLINK: LsErc20TokenDef = { logo: StaticAssetPath.LIQUID_STAKING_TOKEN_PHALA, token: LsToken.LINK, decimals: 18, - // TODO: Use Liquifier's testnet address if the environment is development. - address: '0x514910771AF9Ca656af840dff83E8264EcF986CA', + address: IS_PRODUCTION_ENV + ? '0x514910771AF9Ca656af840dff83E8264EcF986CA' + : SEPOLIA_ERC20_CONTRACT_ADDRESS, // TODO: Use the actual Chainlink Liquifier Adapter address. This is likely deployed to a testnet (Tenderly?). - liquifierAdapterAddress: '0x', - liquifierTgTokenAddress: '0x', + liquifierAdapterAddress: IS_PRODUCTION_ENV + ? '0x' + : SEPOLIA_LIQUIFIER_CONTRACT_ADDRESS, + liquifierTgTokenAddress: IS_PRODUCTION_ENV + ? '0x' + : SEPOLIA_TG_TOKEN_CONTRACT_ADDRESS, timeUnit: CrossChainTimeUnit.DAY, unstakingPeriod: 7, }; @@ -39,11 +53,16 @@ const THE_GRAPH: LsErc20TokenDef = { logo: StaticAssetPath.LIQUID_STAKING_TOKEN_PHALA, token: LsToken.GRT, decimals: 18, - // TODO: Use Liquifier's testnet address if the environment is development. - address: '0xc944E90C64B2c07662A292be6244BDf05Cda44a7', + address: IS_PRODUCTION_ENV + ? '0xc944E90C64B2c07662A292be6244BDf05Cda44a7' + : SEPOLIA_ERC20_CONTRACT_ADDRESS, // TODO: Use the actual Chainlink Liquifier Adapter address. This is likely deployed to a testnet (Tenderly?). - liquifierAdapterAddress: '0x', - liquifierTgTokenAddress: '0x', + liquifierAdapterAddress: IS_PRODUCTION_ENV + ? '0x' + : SEPOLIA_LIQUIFIER_CONTRACT_ADDRESS, + liquifierTgTokenAddress: IS_PRODUCTION_ENV + ? '0x' + : SEPOLIA_TG_TOKEN_CONTRACT_ADDRESS, timeUnit: CrossChainTimeUnit.DAY, unstakingPeriod: 28, }; @@ -56,11 +75,16 @@ const LIVEPEER: LsErc20TokenDef = { logo: StaticAssetPath.LIQUID_STAKING_TOKEN_PHALA, token: LsToken.LPT, decimals: 18, - // TODO: Use Liquifier's testnet address if the environment is development. - address: '0x58b6A8A3302369DAEc383334672404Ee733aB239', + address: IS_PRODUCTION_ENV + ? '0x58b6A8A3302369DAEc383334672404Ee733aB239' + : SEPOLIA_ERC20_CONTRACT_ADDRESS, // TODO: Use the actual Chainlink Liquifier Adapter address. This is likely deployed to a testnet (Tenderly?). - liquifierAdapterAddress: '0x', - liquifierTgTokenAddress: '0x', + liquifierAdapterAddress: IS_PRODUCTION_ENV + ? '0x' + : SEPOLIA_LIQUIFIER_CONTRACT_ADDRESS, + liquifierTgTokenAddress: IS_PRODUCTION_ENV + ? '0x' + : SEPOLIA_TG_TOKEN_CONTRACT_ADDRESS, timeUnit: CrossChainTimeUnit.LIVEPEER_ROUND, unstakingPeriod: 7, }; @@ -69,15 +93,20 @@ const POLYGON: LsErc20TokenDef = { type: 'erc20', id: LsProtocolId.POLYGON, name: 'Polygon', - // TODO: Add logo and link it here. logo: StaticAssetPath.LIQUID_STAKING_TOKEN_PHALA, token: LsToken.POL, decimals: 18, // TODO: Use Liquifier's testnet address if the environment is development. - address: '0x0D500B1d8E8eF31E21C99d1Db9A6444d3ADf1270', + address: IS_PRODUCTION_ENV + ? '0x0D500B1d8E8eF31E21C99d1Db9A6444d3ADf1270' + : SEPOLIA_ERC20_CONTRACT_ADDRESS, // TODO: Use the actual Chainlink Liquifier Adapter address. This is likely deployed to a testnet (Tenderly?). - liquifierAdapterAddress: '0x', - liquifierTgTokenAddress: '0x', + liquifierAdapterAddress: IS_PRODUCTION_ENV + ? '0x' + : SEPOLIA_LIQUIFIER_CONTRACT_ADDRESS, + liquifierTgTokenAddress: IS_PRODUCTION_ENV + ? '0x' + : SEPOLIA_TG_TOKEN_CONTRACT_ADDRESS, timeUnit: CrossChainTimeUnit.POLYGON_CHECKPOINT, unstakingPeriod: 82, }; diff --git a/apps/tangle-dapp/data/liquifier/useContract.ts b/apps/tangle-dapp/data/liquifier/useContract.ts index d95f1a1a9..0c79f00f4 100644 --- a/apps/tangle-dapp/data/liquifier/useContract.ts +++ b/apps/tangle-dapp/data/liquifier/useContract.ts @@ -11,12 +11,13 @@ import { waitForTransactionReceipt, writeContract, } from 'viem/actions'; -import { mainnet } from 'viem/chains'; +import { mainnet, sepolia } from 'viem/chains'; import { useConnectorClient } from 'wagmi'; import { ReadContractReturnType } from 'wagmi/actions'; +import { IS_PRODUCTION_ENV } from '../../constants/env'; import useEvmAddress20 from '../../hooks/useEvmAddress'; -import useEthereumMainnetClient from './useEthereumMainnetClient'; +import useViemPublicClientWithChain from './useViemPublicClientWithChain'; export type ContractReadOptions< Abi extends ViemAbi, @@ -37,7 +38,11 @@ export type ContractWriteOptions< }; const useContract = (abi: Abi) => { - const publicClient = useEthereumMainnetClient(); + // Use Sepolia testnet for development, and mainnet for production. + // Some dummy contracts were deployed on Sepolia for testing purposes. + const chain = IS_PRODUCTION_ENV ? mainnet : sepolia; + + const publicClient = useViemPublicClientWithChain(chain); const { data: connectorClient } = useConnectorClient(); const activeEvmAddress20 = useEvmAddress20(); diff --git a/apps/tangle-dapp/data/liquifier/useLiquifierDeposit.ts b/apps/tangle-dapp/data/liquifier/useLiquifierDeposit.ts index 680bc7036..1a4e9a24e 100644 --- a/apps/tangle-dapp/data/liquifier/useLiquifierDeposit.ts +++ b/apps/tangle-dapp/data/liquifier/useLiquifierDeposit.ts @@ -37,13 +37,13 @@ const useLiquifierDeposit = () => { notifyProcessing: notifyApproveProcessing, notifySuccess: notifyApproveSuccess, notifyError: notifyApproveError, - } = useTxNotification(TxName.LST_LIQUIFIER_APPROVE); + } = useTxNotification(TxName.LS_LIQUIFIER_APPROVE); const { notifyProcessing: notifyDepositProcessing, notifySuccess: notifyDepositSuccess, notifyError: notifyDepositError, - } = useTxNotification(TxName.LST_LIQUIFIER_DEPOSIT); + } = useTxNotification(TxName.LS_LIQUIFIER_DEPOSIT); const isReady = writeLiquifier !== null && diff --git a/apps/tangle-dapp/data/liquifier/useLiquifierUnlock.ts b/apps/tangle-dapp/data/liquifier/useLiquifierUnlock.ts new file mode 100644 index 000000000..cc6448170 --- /dev/null +++ b/apps/tangle-dapp/data/liquifier/useLiquifierUnlock.ts @@ -0,0 +1,61 @@ +import { BN } from '@polkadot/util'; +import assert from 'assert'; +import { useCallback } from 'react'; + +import { TxName } from '../../constants'; +import { LS_ERC20_TOKEN_MAP } from '../../constants/liquidStaking/constants'; +import LIQUIFIER_ABI from '../../constants/liquidStaking/liquifierAbi'; +import { LsErc20TokenId } from '../../constants/liquidStaking/types'; +import useEvmAddress20 from '../../hooks/useEvmAddress'; +import useTxNotification from '../../hooks/useTxNotification'; +import useContract from './useContract'; + +const useLiquifierUnlock = () => { + const activeEvmAddress20 = useEvmAddress20(); + const { write: writeLiquifier } = useContract(LIQUIFIER_ABI); + + const { notifyProcessing, notifySuccess, notifyError } = useTxNotification( + TxName.LS_LIQUIFIER_UNLOCK, + ); + + const isReady = writeLiquifier !== null && activeEvmAddress20 !== null; + + const unlock = useCallback( + async (tokenId: LsErc20TokenId, amount: BN) => { + // TODO: Should the user balance check be done here or assume that the consumer of the hook will handle that? + + assert( + isReady, + 'Should not be able to call this function if the requirements are not ready yet', + ); + + const tokenDef = LS_ERC20_TOKEN_MAP[tokenId]; + + notifyProcessing(); + + const unlockTxReceipt = await writeLiquifier({ + // TODO: Does the adapter contract have a unlock function? It doesn't seem like so. In that case, will need to update the way that Liquifier contract's address is handled. + address: tokenDef.liquifierAdapterAddress, + functionName: 'unlock', + args: [BigInt(amount.toString())], + }); + + if (unlockTxReceipt.status === 'reverted') { + notifyError('Failed to unlock the token amount from the Liquifier'); + + return false; + } + + notifySuccess(unlockTxReceipt.transactionHash); + + return true; + }, + [isReady, notifyError, notifyProcessing, notifySuccess, writeLiquifier], + ); + + // Wait for the requirements to be ready before + // returning the unlock function. + return !isReady ? null : unlock; +}; + +export default useLiquifierUnlock; diff --git a/apps/tangle-dapp/data/liquifier/useEthereumMainnetClient.ts b/apps/tangle-dapp/data/liquifier/useViemPublicClientWithChain.ts similarity index 57% rename from apps/tangle-dapp/data/liquifier/useEthereumMainnetClient.ts rename to apps/tangle-dapp/data/liquifier/useViemPublicClientWithChain.ts index 03eadad41..b5f470c37 100644 --- a/apps/tangle-dapp/data/liquifier/useEthereumMainnetClient.ts +++ b/apps/tangle-dapp/data/liquifier/useViemPublicClientWithChain.ts @@ -1,20 +1,19 @@ import { useEffect, useState } from 'react'; -import { createPublicClient, http, PublicClient } from 'viem'; -import { mainnet } from 'viem/chains'; +import { Chain, createPublicClient, http, PublicClient } from 'viem'; -const useEthereumMainnetClient = () => { +const useViemPublicClientWithChain = (chain: Chain) => { const [publicClient, setPublicClient] = useState(null); useEffect(() => { const newPublicClient = createPublicClient({ - chain: mainnet, + chain, transport: http(), }); setPublicClient(newPublicClient); - }, []); + }, [chain]); return publicClient; }; -export default useEthereumMainnetClient; +export default useViemPublicClientWithChain; diff --git a/apps/tangle-dapp/hooks/useTxNotification.tsx b/apps/tangle-dapp/hooks/useTxNotification.tsx index f78fd88b4..20283f357 100644 --- a/apps/tangle-dapp/hooks/useTxNotification.tsx +++ b/apps/tangle-dapp/hooks/useTxNotification.tsx @@ -32,8 +32,9 @@ const SUCCESS_MESSAGES: Record = { [TxName.LST_REDEEM]: 'Redeem request submitted', [TxName.LST_REBOND]: 'Unstake request cancelled', [TxName.LST_WITHDRAW_REDEEM]: 'Unstake request executed', - [TxName.LST_LIQUIFIER_DEPOSIT]: 'Liquifier deposit successful', - [TxName.LST_LIQUIFIER_APPROVE]: 'Liquifier approval successful', + [TxName.LS_LIQUIFIER_DEPOSIT]: 'Liquifier deposit successful', + [TxName.LS_LIQUIFIER_APPROVE]: 'Liquifier approval successful', + [TxName.LS_LIQUIFIER_UNLOCK]: 'Liquifier unlock successful', }; // TODO: Use a ref for the key to permit multiple rapid fire transactions from stacking under the same key. Otherwise, use a global state counter via Zustand. From 481478a64b9d4bf8c831a943e8fd44dd2e92da02 Mon Sep 17 00:00:00 2001 From: yurixander <101931215+yurixander@users.noreply.github.com> Date: Wed, 21 Aug 2024 03:32:12 -0400 Subject: [PATCH 28/60] refactor(tangle-dapp): Organize development contract addresses --- .../constants/liquidStaking/constants.ts | 41 +++++++++++-------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/apps/tangle-dapp/constants/liquidStaking/constants.ts b/apps/tangle-dapp/constants/liquidStaking/constants.ts index de5ae30e1..37447b511 100644 --- a/apps/tangle-dapp/constants/liquidStaking/constants.ts +++ b/apps/tangle-dapp/constants/liquidStaking/constants.ts @@ -16,12 +16,17 @@ import { LsToken, } from './types'; -const SEPOLIA_LIQUIFIER_CONTRACT_ADDRESS: HexString = - '0xCE0148DbA55c9719B59642f5cBf4F26e67F44E70'; - -// TODO: Fill in the actual addresses once deployed to a testnet. -const SEPOLIA_ERC20_CONTRACT_ADDRESS: HexString = '0x'; -const SEPOLIA_TG_TOKEN_CONTRACT_ADDRESS: HexString = '0x'; +/** + * Development only. Sepolia testnet contracts that were + * deployed to test the liquifier functionality. These contracts + * use dummy data. + */ +const SEPOLIA_TESTNET_CONTRACTS = { + LIQUIFIER: '0xCE0148DbA55c9719B59642f5cBf4F26e67F44E70', + // TODO: Fill in the actual addresses once deployed to a testnet. + ERC20: '0x', + TG_TOKEN: '0x', +} as const satisfies Record; const CHAINLINK: LsErc20TokenDef = { type: 'erc20', @@ -33,14 +38,14 @@ const CHAINLINK: LsErc20TokenDef = { decimals: 18, address: IS_PRODUCTION_ENV ? '0x514910771AF9Ca656af840dff83E8264EcF986CA' - : SEPOLIA_ERC20_CONTRACT_ADDRESS, + : SEPOLIA_TESTNET_CONTRACTS.ERC20, // TODO: Use the actual Chainlink Liquifier Adapter address. This is likely deployed to a testnet (Tenderly?). liquifierAdapterAddress: IS_PRODUCTION_ENV ? '0x' - : SEPOLIA_LIQUIFIER_CONTRACT_ADDRESS, + : SEPOLIA_TESTNET_CONTRACTS.LIQUIFIER, liquifierTgTokenAddress: IS_PRODUCTION_ENV ? '0x' - : SEPOLIA_TG_TOKEN_CONTRACT_ADDRESS, + : SEPOLIA_TESTNET_CONTRACTS.TG_TOKEN, timeUnit: CrossChainTimeUnit.DAY, unstakingPeriod: 7, }; @@ -55,14 +60,14 @@ const THE_GRAPH: LsErc20TokenDef = { decimals: 18, address: IS_PRODUCTION_ENV ? '0xc944E90C64B2c07662A292be6244BDf05Cda44a7' - : SEPOLIA_ERC20_CONTRACT_ADDRESS, + : SEPOLIA_TESTNET_CONTRACTS.ERC20, // TODO: Use the actual Chainlink Liquifier Adapter address. This is likely deployed to a testnet (Tenderly?). liquifierAdapterAddress: IS_PRODUCTION_ENV ? '0x' - : SEPOLIA_LIQUIFIER_CONTRACT_ADDRESS, + : SEPOLIA_TESTNET_CONTRACTS.LIQUIFIER, liquifierTgTokenAddress: IS_PRODUCTION_ENV ? '0x' - : SEPOLIA_TG_TOKEN_CONTRACT_ADDRESS, + : SEPOLIA_TESTNET_CONTRACTS.TG_TOKEN, timeUnit: CrossChainTimeUnit.DAY, unstakingPeriod: 28, }; @@ -77,14 +82,14 @@ const LIVEPEER: LsErc20TokenDef = { decimals: 18, address: IS_PRODUCTION_ENV ? '0x58b6A8A3302369DAEc383334672404Ee733aB239' - : SEPOLIA_ERC20_CONTRACT_ADDRESS, + : SEPOLIA_TESTNET_CONTRACTS.ERC20, // TODO: Use the actual Chainlink Liquifier Adapter address. This is likely deployed to a testnet (Tenderly?). liquifierAdapterAddress: IS_PRODUCTION_ENV ? '0x' - : SEPOLIA_LIQUIFIER_CONTRACT_ADDRESS, + : SEPOLIA_TESTNET_CONTRACTS.LIQUIFIER, liquifierTgTokenAddress: IS_PRODUCTION_ENV ? '0x' - : SEPOLIA_TG_TOKEN_CONTRACT_ADDRESS, + : SEPOLIA_TESTNET_CONTRACTS.TG_TOKEN, timeUnit: CrossChainTimeUnit.LIVEPEER_ROUND, unstakingPeriod: 7, }; @@ -99,14 +104,14 @@ const POLYGON: LsErc20TokenDef = { // TODO: Use Liquifier's testnet address if the environment is development. address: IS_PRODUCTION_ENV ? '0x0D500B1d8E8eF31E21C99d1Db9A6444d3ADf1270' - : SEPOLIA_ERC20_CONTRACT_ADDRESS, + : SEPOLIA_TESTNET_CONTRACTS.ERC20, // TODO: Use the actual Chainlink Liquifier Adapter address. This is likely deployed to a testnet (Tenderly?). liquifierAdapterAddress: IS_PRODUCTION_ENV ? '0x' - : SEPOLIA_LIQUIFIER_CONTRACT_ADDRESS, + : SEPOLIA_TESTNET_CONTRACTS.LIQUIFIER, liquifierTgTokenAddress: IS_PRODUCTION_ENV ? '0x' - : SEPOLIA_TG_TOKEN_CONTRACT_ADDRESS, + : SEPOLIA_TESTNET_CONTRACTS.TG_TOKEN, timeUnit: CrossChainTimeUnit.POLYGON_CHECKPOINT, unstakingPeriod: 82, }; From 11664aa678f7f211c6eacde585f7e0d64acf7ec7 Mon Sep 17 00:00:00 2001 From: yurixander <101931215+yurixander@users.noreply.github.com> Date: Wed, 21 Aug 2024 03:41:24 -0400 Subject: [PATCH 29/60] ci(tangle-dapp): Fix lint issue --- .../LiquidStaking/stakeAndUnstake/useLsSpendingLimits.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/useLsSpendingLimits.ts b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/useLsSpendingLimits.ts index f070f2a77..0fa5f02f3 100644 --- a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/useLsSpendingLimits.ts +++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/useLsSpendingLimits.ts @@ -1,12 +1,12 @@ 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 { LsProtocolId } from '../../../constants/liquidStaking/types'; import useApi from '../../../hooks/useApi'; import useApiRx from '../../../hooks/useApiRx'; -import useAgnosticLsBalance from './useAgnosticLsBalance'; -import { TANGLE_RESTAKING_PARACHAIN_LOCAL_DEV_NETWORK } from '../../../../../libs/webb-ui-components/src/constants/networks'; import getLsProtocolDef from '../../../utils/liquidStaking/getLsProtocolDef'; +import useAgnosticLsBalance from './useAgnosticLsBalance'; const useLsSpendingLimits = (isNative: boolean, protocolId: LsProtocolId) => { const balance = useAgnosticLsBalance(isNative, protocolId); From 96ffa3d8b02a99deb1981ace153bd5b6e55449b9 Mon Sep 17 00:00:00 2001 From: yurixander <101931215+yurixander@users.noreply.github.com> Date: Wed, 21 Aug 2024 22:28:45 -0400 Subject: [PATCH 30/60] refactor(tangle-dapp): Expand `useContract` into `useContractWrite` and `useContractRead` --- .../stakeAndUnstake/LiquidStakeCard.tsx | 10 +- .../stakeAndUnstake/useAgnosticLsBalance.ts | 6 +- .../constants/liquidStaking/constants.ts | 2 +- .../data/liquidStaking/useExchangeRate.ts | 22 +++- .../data/liquidStaking/usePolling.ts | 4 +- .../tangle-dapp/data/liquifier/useContract.ts | 122 ------------------ .../data/liquifier/useContractRead.ts | 58 +++++++++ .../liquifier/useContractReadSubscription.ts | 19 ++- .../data/liquifier/useContractWrite.ts | 89 +++++++++++++ .../data/liquifier/useLiquifierDeposit.ts | 50 +------ .../liquifier/useLiquifierErc20Balance.ts | 4 +- .../data/liquifier/useLiquifierTgBalance.ts | 4 +- .../data/liquifier/useLiquifierUnlock.ts | 24 +--- apps/tangle-dapp/hooks/useAgnosticTx.ts | 10 +- apps/tangle-dapp/hooks/useSubstrateTx.ts | 18 ++- apps/tangle-dapp/hooks/useTxNotification.tsx | 43 +++--- 16 files changed, 250 insertions(+), 235 deletions(-) delete mode 100644 apps/tangle-dapp/data/liquifier/useContract.ts create mode 100644 apps/tangle-dapp/data/liquifier/useContractRead.ts create mode 100644 apps/tangle-dapp/data/liquifier/useContractWrite.ts diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakeCard.tsx b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakeCard.tsx index 34e792c92..05dc467ad 100644 --- a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakeCard.tsx +++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakeCard.tsx @@ -98,6 +98,12 @@ const LiquidStakeCard: FC = () => { return fromAmount.muln(exchangeRate); }, [fromAmount, exchangeRate]); + const isReady = + (fromAmount !== null && + selectedProtocol.type === 'parachain' && + executeMintTx !== null) || + (selectedProtocol.type === 'erc20' && performLiquifierDeposit !== null); + const walletBalance = ( {
            diff --git a/apps/tangle-dapp/app/blueprints/[name]/TvlTable.tsx b/apps/tangle-dapp/app/blueprints/[name]/TvlTable.tsx index d552cea8e..c6da65e0c 100644 --- a/apps/tangle-dapp/app/blueprints/[name]/TvlTable.tsx +++ b/apps/tangle-dapp/app/blueprints/[name]/TvlTable.tsx @@ -21,7 +21,7 @@ import Link from 'next/link'; import { FC, useCallback, useState } from 'react'; import { twMerge } from 'tailwind-merge'; -import LSTTokenIcon from '../../../components/LSTTokenIcon'; +import LsTokenIcon from '../../../components/LsTokenIcon'; import { Vault } from '../../../types/blueprint'; import TableCellWrapper from './TableCellWrapper'; import useVaults from './useVaults'; @@ -35,7 +35,7 @@ const columns = [ cell: (props) => (
            - + {props.getValue()} diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakingInput.tsx b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakingInput.tsx index 5a67a3b6b..d5584d2ed 100644 --- a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakingInput.tsx +++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakingInput.tsx @@ -165,7 +165,7 @@ const ProtocolSelector: FC = ({ const base = (
            - + {selectedProtocol.name} @@ -181,8 +181,8 @@ const ProtocolSelector: FC = ({ {base} - -
              + +
                {Object.values(LsProtocolId) .filter( (protocolId): protocolId is LsProtocolId => @@ -195,7 +195,12 @@ const ProtocolSelector: FC = ({ return (
              • } + leftIcon={ + + } onSelect={() => setProtocolId(protocolId)} className="px-3 normal-case" > diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakingTokenItem.tsx b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakingTokenItem.tsx index 04d4b59a9..4ae4efe47 100644 --- a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakingTokenItem.tsx +++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakingTokenItem.tsx @@ -6,7 +6,6 @@ import { Button, Chip, Typography } from '@webb-tools/webb-ui-components'; import Image from 'next/image'; import { FC, useMemo } from 'react'; -import LSTTokenIcon from '../../../components/LSTTokenIcon'; import { StaticAssetPath } from '../../../constants'; import { LST_PREFIX, @@ -15,6 +14,7 @@ import { import { LsToken } from '../../../constants/liquidStaking/types'; import { PagePath } from '../../../types'; import formatTangleBalance from '../../../utils/formatTangleBalance'; +import LsTokenIcon from '../../LsTokenIcon'; import StatItem from '../StatItem'; export type LiquidStakingTokenItemProps = { @@ -44,7 +44,7 @@ const LiquidStakingTokenItem: FC = ({
                - + = ({ {/* Information */}
                {/* TODO: get list of token dynamically */} - +
                diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/TokenChip.tsx b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/TokenChip.tsx index 03f2ae4fa..aa8d04212 100644 --- a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/TokenChip.tsx +++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/TokenChip.tsx @@ -4,7 +4,7 @@ import { twMerge } from 'tailwind-merge'; import { LST_PREFIX } from '../../../constants/liquidStaking/constants'; import { LsToken } from '../../../constants/liquidStaking/types'; -import LSTTokenIcon from '../../LSTTokenIcon'; +import LsTokenIcon from '../../LsTokenIcon'; import DropdownChevronIcon from './DropdownChevronIcon'; type TokenChipProps = { @@ -22,7 +22,7 @@ const TokenChip: FC = ({ token, isLiquidVariant, onClick }) => { onClick !== undefined && 'cursor-pointer', )} > - {token && } + {token && } {isLiquidVariant && LST_PREFIX} diff --git a/apps/tangle-dapp/components/LSTTokenIcon.tsx b/apps/tangle-dapp/components/LsTokenIcon.tsx similarity index 91% rename from apps/tangle-dapp/components/LSTTokenIcon.tsx rename to apps/tangle-dapp/components/LsTokenIcon.tsx index 9a6ad1cb1..d9388265a 100644 --- a/apps/tangle-dapp/components/LSTTokenIcon.tsx +++ b/apps/tangle-dapp/components/LsTokenIcon.tsx @@ -4,15 +4,16 @@ import { twMerge } from 'tailwind-merge'; type LSTTokenIconSize = 'md' | 'lg'; -interface LSTTokenIconProps { +interface LsTokenIconProps { name: string; size?: LSTTokenIconSize; } -const LSTTokenIcon: FC = ({ name, size = 'md' }) => { +const LsTokenIcon: FC = ({ name, size = 'md' }) => { const { wrapperSizeClassName, iconSizeClassName, borderSize } = getSizeValues(size); + // TODO: Positioning of the logo is not 100% centered; a few pixels off. return (
                = ({ name, size = 'md' }) => { ); }; -export default LSTTokenIcon; +export default LsTokenIcon; function getSizeValues(size: LSTTokenIconSize): { wrapperSizeClassName: string; diff --git a/apps/tangle-dapp/constants/index.ts b/apps/tangle-dapp/constants/index.ts index 395f99c60..615fd0818 100644 --- a/apps/tangle-dapp/constants/index.ts +++ b/apps/tangle-dapp/constants/index.ts @@ -24,12 +24,6 @@ export enum StaticAssetPath { RESTAKING_METHOD_SHARED_DARK = '/static/assets/restaking/method-shared-dark.svg', RESTAKING_METHOD_INDEPENDENT_LIGHT = '/static/assets/restaking/method-independent-light.svg', RESTAKING_METHOD_SHARED_LIGHT = '/static/assets/restaking/method-shared-light.svg', - LIQUID_STAKING_TOKEN_ASTAR = '/static/assets/liquidStaking/token-astar.svg', - LIQUID_STAKING_TOKEN_GLIMMER = '/static/assets/liquidStaking/token-glimmer.svg', - LIQUID_STAKING_TOKEN_MANTA = '/static/assets/liquidStaking/token-manta.svg', - LIQUID_STAKING_TOKEN_PHALA = '/static/assets/liquidStaking/token-phala.svg', - LIQUID_STAKING_TOKEN_POLKADOT = '/static/assets/liquidStaking/token-polkadot.svg', - LIQUID_STAKING_TANGLE_LOGO = '/static/assets/liquidStaking/tangle-logo.svg', } export enum ChartColor { diff --git a/apps/tangle-dapp/constants/liquidStaking/constants.ts b/apps/tangle-dapp/constants/liquidStaking/constants.ts index 799ef7088..584b3beed 100644 --- a/apps/tangle-dapp/constants/liquidStaking/constants.ts +++ b/apps/tangle-dapp/constants/liquidStaking/constants.ts @@ -3,7 +3,6 @@ import { TANGLE_TOKEN_DECIMALS } from '@webb-tools/dapp-config'; import { TANGLE_RESTAKING_PARACHAIN_LOCAL_DEV_NETWORK } from '@webb-tools/webb-ui-components/constants/networks'; import { CrossChainTimeUnit } from '../../utils/CrossChainTime'; -import { StaticAssetPath } from '..'; import { IS_PRODUCTION_ENV } from '../env'; import { LsErc20TokenDef, @@ -32,8 +31,7 @@ const CHAINLINK: LsErc20TokenDef = { type: 'erc20', id: LsProtocolId.CHAINLINK, name: 'Chainlink', - // TODO: Add logo and link it here. - logo: StaticAssetPath.LIQUID_STAKING_TOKEN_PHALA, + chainIconFileName: 'chainlink', token: LsToken.LINK, decimals: 18, address: IS_PRODUCTION_ENV @@ -54,8 +52,7 @@ const THE_GRAPH: LsErc20TokenDef = { type: 'erc20', id: LsProtocolId.THE_GRAPH, name: 'The Graph', - // TODO: Add logo and link it here. - logo: StaticAssetPath.LIQUID_STAKING_TOKEN_PHALA, + chainIconFileName: 'the-graph', token: LsToken.GRT, decimals: 18, address: IS_PRODUCTION_ENV @@ -76,8 +73,7 @@ const LIVEPEER: LsErc20TokenDef = { type: 'erc20', id: LsProtocolId.LIVEPEER, name: 'Livepeer', - // TODO: Add logo and link it here. - logo: StaticAssetPath.LIQUID_STAKING_TOKEN_PHALA, + chainIconFileName: 'livepeer', token: LsToken.LPT, decimals: 18, address: IS_PRODUCTION_ENV @@ -98,7 +94,7 @@ const POLYGON: LsErc20TokenDef = { type: 'erc20', id: LsProtocolId.POLYGON, name: 'Polygon', - logo: StaticAssetPath.LIQUID_STAKING_TOKEN_PHALA, + chainIconFileName: 'polygon', token: LsToken.POL, decimals: 18, // TODO: Use Liquifier's testnet address if the environment is development. @@ -121,7 +117,7 @@ const POLKADOT: LsParachainChainDef = { id: LsProtocolId.POLKADOT, name: 'Polkadot', token: LsToken.DOT, - logo: StaticAssetPath.LIQUID_STAKING_TOKEN_POLKADOT, + chainIconFileName: 'polkadot', currency: 'Dot', decimals: 10, rpcEndpoint: 'wss://polkadot-rpc.dwellir.com', @@ -134,7 +130,7 @@ const PHALA: LsParachainChainDef = { id: LsProtocolId.PHALA, name: 'Phala', token: LsToken.PHALA, - logo: StaticAssetPath.LIQUID_STAKING_TOKEN_PHALA, + chainIconFileName: 'phala', currency: 'Pha', decimals: 18, rpcEndpoint: 'wss://api.phala.network/ws', @@ -147,7 +143,7 @@ const MOONBEAM: LsParachainChainDef = { id: LsProtocolId.MOONBEAM, name: 'Moonbeam', token: LsToken.GLMR, - logo: StaticAssetPath.LIQUID_STAKING_TOKEN_GLIMMER, + chainIconFileName: 'moonbeam', // TODO: No currency entry for GLMR in the Tangle Primitives? currency: 'Dot', decimals: 18, @@ -161,7 +157,7 @@ const ASTAR: LsParachainChainDef = { id: LsProtocolId.ASTAR, name: 'Astar', token: LsToken.ASTAR, - logo: StaticAssetPath.LIQUID_STAKING_TOKEN_ASTAR, + chainIconFileName: 'astar', // TODO: No currency entry for ASTAR in the Tangle Primitives? currency: 'Dot', decimals: 18, @@ -175,7 +171,7 @@ const MANTA: LsParachainChainDef = { id: LsProtocolId.MANTA, name: 'Manta', token: LsToken.MANTA, - logo: StaticAssetPath.LIQUID_STAKING_TOKEN_MANTA, + chainIconFileName: 'manta', // TODO: No currency entry for ASTAR in the Tangle Primitives? currency: 'Dot', decimals: 18, @@ -189,7 +185,7 @@ const TANGLE_RESTAKING_PARACHAIN: LsParachainChainDef = { id: LsProtocolId.TANGLE_RESTAKING_PARACHAIN, name: 'Tangle Parachain', token: LsToken.TNT, - logo: StaticAssetPath.LIQUID_STAKING_TANGLE_LOGO, + chainIconFileName: 'tangle', currency: 'Bnc', decimals: TANGLE_TOKEN_DECIMALS, rpcEndpoint: TANGLE_RESTAKING_PARACHAIN_LOCAL_DEV_NETWORK.wsRpcEndpoint, diff --git a/apps/tangle-dapp/constants/liquidStaking/types.ts b/apps/tangle-dapp/constants/liquidStaking/types.ts index fa863140e..179a58ceb 100644 --- a/apps/tangle-dapp/constants/liquidStaking/types.ts +++ b/apps/tangle-dapp/constants/liquidStaking/types.ts @@ -55,7 +55,7 @@ type ProtocolDefCommon = { decimals: number; timeUnit: CrossChainTimeUnit; unstakingPeriod: number; - logo: StaticAssetPath; + chainIconFileName: string; }; export interface LsParachainChainDef extends ProtocolDefCommon { diff --git a/apps/tangle-dapp/data/liquifier/useContractWrite.ts b/apps/tangle-dapp/data/liquifier/useContractWrite.ts index 2cb2731a0..4e86f5ef1 100644 --- a/apps/tangle-dapp/data/liquifier/useContractWrite.ts +++ b/apps/tangle-dapp/data/liquifier/useContractWrite.ts @@ -11,10 +11,11 @@ import { waitForTransactionReceipt, writeContract, } from 'viem/actions'; -import { mainnet } from 'viem/chains'; +import { mainnet, sepolia } from 'viem/chains'; import { useConnectorClient } from 'wagmi'; import { TxName } from '../../constants'; +import { IS_PRODUCTION_ENV } from '../../constants/env'; import useEvmAddress20 from '../../hooks/useEvmAddress'; import useTxNotification from '../../hooks/useTxNotification'; @@ -59,7 +60,7 @@ const useContractWrite = (abi: Abi) => { // TODO: Handle possible errors thrown by `simulateContract`. const { request } = await simulateContract(connectorClient, { - chain: mainnet, + chain: IS_PRODUCTION_ENV ? mainnet : sepolia, address: options.address, functionName: options.functionName, account: activeEvmAddress20, diff --git a/apps/tangle-dapp/public/static/assets/liquidStaking/tangle-logo.svg b/apps/tangle-dapp/public/static/assets/liquidStaking/tangle-logo.svg deleted file mode 100644 index a8ac33a96..000000000 --- a/apps/tangle-dapp/public/static/assets/liquidStaking/tangle-logo.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/apps/tangle-dapp/public/static/assets/liquidStaking/token-astar.svg b/apps/tangle-dapp/public/static/assets/liquidStaking/token-astar.svg deleted file mode 100644 index 8cb3043ce..000000000 --- a/apps/tangle-dapp/public/static/assets/liquidStaking/token-astar.svg +++ /dev/null @@ -1,142 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/apps/tangle-dapp/public/static/assets/liquidStaking/token-glimmer.svg b/apps/tangle-dapp/public/static/assets/liquidStaking/token-glimmer.svg deleted file mode 100644 index 122e59648..000000000 --- a/apps/tangle-dapp/public/static/assets/liquidStaking/token-glimmer.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/apps/tangle-dapp/public/static/assets/liquidStaking/token-manta.svg b/apps/tangle-dapp/public/static/assets/liquidStaking/token-manta.svg deleted file mode 100644 index 1115ee541..000000000 --- a/apps/tangle-dapp/public/static/assets/liquidStaking/token-manta.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - diff --git a/apps/tangle-dapp/public/static/assets/liquidStaking/token-phala.svg b/apps/tangle-dapp/public/static/assets/liquidStaking/token-phala.svg deleted file mode 100644 index ec0a2bb79..000000000 --- a/apps/tangle-dapp/public/static/assets/liquidStaking/token-phala.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - diff --git a/apps/tangle-dapp/public/static/assets/liquidStaking/token-polkadot.svg b/apps/tangle-dapp/public/static/assets/liquidStaking/token-polkadot.svg deleted file mode 100644 index 5c9ddef7d..000000000 --- a/apps/tangle-dapp/public/static/assets/liquidStaking/token-polkadot.svg +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/libs/icons/src/chains/astar.svg b/libs/icons/src/chains/astar.svg index 0796603e4..55a78a51a 100644 --- a/libs/icons/src/chains/astar.svg +++ b/libs/icons/src/chains/astar.svg @@ -1,141 +1,222 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/libs/icons/src/chains/livepeer.svg b/libs/icons/src/chains/livepeer.svg new file mode 100644 index 000000000..2cfd2c21b --- /dev/null +++ b/libs/icons/src/chains/livepeer.svg @@ -0,0 +1,12 @@ + + + + + + + + + + diff --git a/libs/icons/src/chains/the-graph.svg b/libs/icons/src/chains/the-graph.svg new file mode 100644 index 000000000..ffe999555 --- /dev/null +++ b/libs/icons/src/chains/the-graph.svg @@ -0,0 +1,6 @@ + + + + diff --git a/libs/icons/src/tokens/lpt.svg b/libs/icons/src/tokens/lpt.svg new file mode 100644 index 000000000..8bce36877 --- /dev/null +++ b/libs/icons/src/tokens/lpt.svg @@ -0,0 +1,12 @@ + + + + + + + + + + From 628f23af0f9faaeb4599e2ed6c500229954f4976 Mon Sep 17 00:00:00 2001 From: yurixander <101931215+yurixander@users.noreply.github.com> Date: Thu, 22 Aug 2024 02:21:24 -0400 Subject: [PATCH 32/60] feat(tangle-dapp): Add step progress to tx notification --- apps/tangle-dapp/constants/index.ts | 2 +- .../data/liquifier/useContractWrite.ts | 106 +++++++++++++----- .../data/liquifier/useLiquifierDeposit.ts | 15 ++- .../data/liquifier/useLiquifierUnlock.ts | 6 +- apps/tangle-dapp/hooks/useTxNotification.tsx | 19 +++- 5 files changed, 104 insertions(+), 44 deletions(-) diff --git a/apps/tangle-dapp/constants/index.ts b/apps/tangle-dapp/constants/index.ts index 615fd0818..29b912984 100644 --- a/apps/tangle-dapp/constants/index.ts +++ b/apps/tangle-dapp/constants/index.ts @@ -57,7 +57,7 @@ export enum TxName { LST_REDEEM = 'redeem', LST_REBOND = 'cancel unstake request', LST_WITHDRAW_REDEEM = 'withdraw redeemed tokens', - LS_LIQUIFIER_APPROVE = 'liquifier approve spending', + LS_LIQUIFIER_APPROVE = 'approve spending for liquifier', LS_LIQUIFIER_DEPOSIT = 'liquifier deposit', LS_LIQUIFIER_UNLOCK = 'liquifier unlock', } diff --git a/apps/tangle-dapp/data/liquifier/useContractWrite.ts b/apps/tangle-dapp/data/liquifier/useContractWrite.ts index 4e86f5ef1..af212a010 100644 --- a/apps/tangle-dapp/data/liquifier/useContractWrite.ts +++ b/apps/tangle-dapp/data/liquifier/useContractWrite.ts @@ -1,4 +1,10 @@ import { HexString } from '@polkadot/util/types'; +import { useWebContext } from '@webb-tools/api-provider-environment'; +import chainsPopulated from '@webb-tools/dapp-config/chains/chainsPopulated'; +import { + calculateTypedChainId, + ChainType, +} from '@webb-tools/sdk-core/typed-chain-id'; import assert from 'assert'; import { useCallback } from 'react'; import { @@ -17,7 +23,10 @@ import { useConnectorClient } from 'wagmi'; import { TxName } from '../../constants'; import { IS_PRODUCTION_ENV } from '../../constants/env'; import useEvmAddress20 from '../../hooks/useEvmAddress'; -import useTxNotification from '../../hooks/useTxNotification'; +import useTxNotification, { + NotificationSteps, +} from '../../hooks/useTxNotification'; +import ensureError from '../../utils/ensureError'; export type ContractWriteOptions< Abi extends ViemAbi, @@ -27,18 +36,14 @@ export type ContractWriteOptions< address: HexString; functionName: FunctionName; args: ContractFunctionArgs; + notificationStep?: NotificationSteps; }; const useContractWrite = (abi: Abi) => { const { data: connectorClient } = useConnectorClient(); const activeEvmAddress20 = useEvmAddress20(); - - // TODO: Make use of success and error notifications. - const { - notifyProcessing, - notifySuccess: _, - notifyError: __, - } = useTxNotification(); + const { activeChain, activeWallet, switchChain } = useWebContext(); + const { notifyProcessing, notifySuccess, notifyError } = useTxNotification(); const write = useCallback( async < @@ -56,28 +61,71 @@ const useContractWrite = (abi: Abi) => { 'Should not be able to call this function if there is no active EVM account', ); - notifyProcessing(options.txName); - - // TODO: Handle possible errors thrown by `simulateContract`. - const { request } = await simulateContract(connectorClient, { - chain: IS_PRODUCTION_ENV ? mainnet : sepolia, - address: options.address, - functionName: options.functionName, - account: activeEvmAddress20, - // TODO: Getting the type of `args` and `abi` right has proven quite difficult. - abi: abi as any, - args: options.args as any, - }); - - const txHash = await writeContract(connectorClient, request); - - // Return the transaction receipt, which contains the transaction status. - return waitForTransactionReceipt(connectorClient, { - hash: txHash, - // TODO: Make use of the `timeout` parameter, and error handle if it fails due to timeout. - }); + // On development, switch to the Sepolia chain if it's not already active. + // This is because there are dummy contracts deployed to Sepolia for testing. + if ( + !IS_PRODUCTION_ENV && + activeChain !== null && + activeChain !== undefined && + activeChain.id !== sepolia.id && + activeWallet !== undefined + ) { + const typedChainId = calculateTypedChainId(ChainType.EVM, sepolia.id); + const targetChain = chainsPopulated[typedChainId]; + + await switchChain(targetChain, activeWallet); + } + + notifyProcessing(options.txName, options.notificationStep); + + try { + const { request } = await simulateContract(connectorClient, { + chain: IS_PRODUCTION_ENV ? mainnet : sepolia, + address: options.address, + functionName: options.functionName, + account: activeEvmAddress20, + // TODO: Getting the type of `args` and `abi` right has proven quite difficult. + abi: abi as any, + args: options.args as any, + }); + + const txHash = await writeContract(connectorClient, request); + + const txReceipt = await waitForTransactionReceipt(connectorClient, { + hash: txHash, + // TODO: Make use of the `timeout` parameter, and error handle if it fails due to timeout. + }); + + if (txReceipt.status === 'success') { + notifySuccess(options.txName, txHash); + } else { + // TODO: Improve UX by at least providing a link to the transaction hash. The idea is that if there was an error, it would have been caught by the try-catch, so this part here is sort of an 'unreachable' section. + notifyError( + options.txName, + `${options.txName} reverted, but no information about the error is known`, + ); + } + + return txReceipt.status === 'success'; + } catch (possibleError) { + const error = ensureError(possibleError); + + notifyError(options.txName, error); + + return false; + } }, - [abi, activeEvmAddress20, connectorClient, notifyProcessing], + [ + abi, + activeChain, + activeEvmAddress20, + activeWallet, + connectorClient, + notifyError, + notifyProcessing, + notifySuccess, + switchChain, + ], ); // Only provide the write function once the connector client is ready, diff --git a/apps/tangle-dapp/data/liquifier/useLiquifierDeposit.ts b/apps/tangle-dapp/data/liquifier/useLiquifierDeposit.ts index 401b7b37b..0550aba5f 100644 --- a/apps/tangle-dapp/data/liquifier/useLiquifierDeposit.ts +++ b/apps/tangle-dapp/data/liquifier/useLiquifierDeposit.ts @@ -48,32 +48,31 @@ const useLiquifierDeposit = () => { const tokenDef = LS_ERC20_TOKEN_MAP[tokenId]; + // TODO: Check for approval first, in case that it has already been granted. This prevents another unnecessary approval transaction (ex. if the transaction fails after the approval but before the deposit). // Approve spending the token amount by the Liquifier contract. - const approveTxReceipt = await writeChainlinkErc20({ + const approveTxSucceeded = await writeChainlinkErc20({ txName: TxName.LS_LIQUIFIER_APPROVE, address: tokenDef.address, functionName: 'approve', args: [tokenDef.liquifierAdapterAddress, BigInt(amount.toString())], + notificationStep: { current: 1, max: 2 }, }); - if (approveTxReceipt.status === 'reverted') { + if (!approveTxSucceeded) { return false; } - const depositTxReceipt = await writeLiquifier({ + const depositTxSucceeded = await writeLiquifier({ txName: TxName.LS_LIQUIFIER_DEPOSIT, // TODO: Does the adapter contract have a deposit function? It doesn't seem like so. In that case, will need to update the way that Liquifier contract's address is handled. address: tokenDef.liquifierAdapterAddress, functionName: 'deposit', // TODO: Provide the first arg. (validator). Need to figure out how it works on Chainlink (vaults? single address?). See: https://github.com/webb-tools/tnt-core/blob/21c158d6cb11e2b5f50409d377431e7cd51ff72f/src/lst/adapters/ChainlinkAdapter.sol#L187 args: [activeEvmAddress20, BigInt(amount.toString())], + notificationStep: { current: 2, max: 2 }, }); - if (depositTxReceipt.status === 'reverted') { - return false; - } - - return true; + return depositTxSucceeded; }, [activeEvmAddress20, isReady, writeChainlinkErc20, writeLiquifier], ); diff --git a/apps/tangle-dapp/data/liquifier/useLiquifierUnlock.ts b/apps/tangle-dapp/data/liquifier/useLiquifierUnlock.ts index e1df72012..43d3ec7c5 100644 --- a/apps/tangle-dapp/data/liquifier/useLiquifierUnlock.ts +++ b/apps/tangle-dapp/data/liquifier/useLiquifierUnlock.ts @@ -16,7 +16,7 @@ const useLiquifierUnlock = () => { const isReady = writeLiquifier !== null && activeEvmAddress20 !== null; const unlock = useCallback( - async (tokenId: LsErc20TokenId, amount: BN) => { + async (tokenId: LsErc20TokenId, amount: BN): Promise => { // TODO: Should the user balance check be done here or assume that the consumer of the hook will handle that? assert( @@ -26,15 +26,13 @@ const useLiquifierUnlock = () => { const tokenDef = LS_ERC20_TOKEN_MAP[tokenId]; - const unlockTxReceipt = await writeLiquifier({ + return writeLiquifier({ txName: TxName.LS_LIQUIFIER_UNLOCK, // TODO: Does the adapter contract have a unlock function? It doesn't seem like so. In that case, will need to update the way that Liquifier contract's address is handled. address: tokenDef.liquifierAdapterAddress, functionName: 'unlock', args: [BigInt(amount.toString())], }); - - return unlockTxReceipt.status !== 'reverted'; }, [isReady, writeLiquifier], ); diff --git a/apps/tangle-dapp/hooks/useTxNotification.tsx b/apps/tangle-dapp/hooks/useTxNotification.tsx index 290ff3355..bd787978c 100644 --- a/apps/tangle-dapp/hooks/useTxNotification.tsx +++ b/apps/tangle-dapp/hooks/useTxNotification.tsx @@ -40,6 +40,11 @@ const SUCCESS_MESSAGES: Record = { const makeKey = (txName: TxName): `${TxName}-tx-notification` => `${txName}-tx-notification`; +export type NotificationSteps = { + current: number; + max: number; +}; + // TODO: Use a ref for the key to permit multiple rapid fire transactions from stacking under the same key. Otherwise, use a global state counter via Zustand. const useTxNotification = (explorerUrl?: string) => { const { enqueueSnackbar, closeSnackbar } = useSnackbar(); @@ -126,13 +131,23 @@ const useTxNotification = (explorerUrl?: string) => { ); const notifyProcessing = useCallback( - (txName: TxName) => { + (txName: TxName, steps?: NotificationSteps) => { + // Sanity check. + if (steps !== undefined && steps.current > steps.max) { + console.warn( + 'Current transaction notification steps exceed the maximum steps (check for off-by-one errors)', + ); + } + const key = makeKey(txName); closeSnackbar(makeKey(txName)); enqueueSnackbar( - Processing {txName}, + + {steps !== undefined && `(${steps.current}/${steps.max}) `}Processing{' '} + {txName} + , { key, variant: 'info', From fd8d639bc488732b486bbc5ad8d5ffcd2d9014d4 Mon Sep 17 00:00:00 2001 From: yurixander <101931215+yurixander@users.noreply.github.com> Date: Thu, 22 Aug 2024 20:12:26 -0400 Subject: [PATCH 33/60] refactor(tangle-dapp): Cleanup liquid staking URL structure --- .../app/liquid-staking/[tokenSymbol]/page.tsx | 65 ------------- .../app/liquid-staking/overview/page.tsx | 57 ++++++++++++ apps/tangle-dapp/app/liquid-staking/page.tsx | 93 +++++++++---------- .../components/Breadcrumbs/utils.tsx | 11 +-- .../LiquidStakingTokenItem.tsx | 2 +- .../stakeAndUnstake/LiquidUnstakeCard.tsx | 1 + .../components/Sidebar/sidebarProps.ts | 2 +- .../constants/liquidStaking/types.ts | 1 - apps/tangle-dapp/types/index.ts | 1 + 9 files changed, 107 insertions(+), 126 deletions(-) delete mode 100644 apps/tangle-dapp/app/liquid-staking/[tokenSymbol]/page.tsx create mode 100644 apps/tangle-dapp/app/liquid-staking/overview/page.tsx diff --git a/apps/tangle-dapp/app/liquid-staking/[tokenSymbol]/page.tsx b/apps/tangle-dapp/app/liquid-staking/[tokenSymbol]/page.tsx deleted file mode 100644 index aed72d8fe..000000000 --- a/apps/tangle-dapp/app/liquid-staking/[tokenSymbol]/page.tsx +++ /dev/null @@ -1,65 +0,0 @@ -'use client'; - -import { notFound } from 'next/navigation'; -import { FC } from 'react'; - -import { LiquidStakingSelectionTable } from '../../../components/LiquidStaking/LiquidStakingSelectionTable'; -import LiquidStakeCard from '../../../components/LiquidStaking/stakeAndUnstake/LiquidStakeCard'; -import LiquidUnstakeCard from '../../../components/LiquidStaking/stakeAndUnstake/LiquidUnstakeCard'; -import UnstakeRequestsTable from '../../../components/LiquidStaking/unstakeRequestsTable/UnstakeRequestsTable'; -import { LsSearchParamKey } from '../../../constants/liquidStaking/types'; -import useSearchParamState from '../../../hooks/useSearchParamState'; -import isLsParachainToken from '../../../utils/liquidStaking/isLsParachainToken'; -import TabListItem from '../../restake/TabListItem'; -import TabsList from '../../restake/TabsList'; - -type Props = { - params: { tokenSymbol: string }; -}; - -enum SearchParamAction { - STAKE = 'stake', - UNSTAKE = 'unstake', -} - -const LiquidStakingTokenPage: FC = ({ params: { tokenSymbol } }) => { - const [isStaking, setIsStaking] = useSearchParamState({ - defaultValue: true, - key: LsSearchParamKey.ACTION, - parser: (value) => value === SearchParamAction.STAKE, - stringify: (value) => - value ? SearchParamAction.STAKE : SearchParamAction.UNSTAKE, - }); - - // An invalid or unknown token symbol was provided on the URL. - if (!isLsParachainToken(tokenSymbol)) { - return notFound(); - } - - return ( -
                -
                - - setIsStaking(true)}> - Stake - - - setIsStaking(false)} - > - Unstake - - - - {isStaking ? : } -
                - -
                - {isStaking ? : } -
                -
                - ); -}; - -export default LiquidStakingTokenPage; diff --git a/apps/tangle-dapp/app/liquid-staking/overview/page.tsx b/apps/tangle-dapp/app/liquid-staking/overview/page.tsx new file mode 100644 index 000000000..34d6a42ed --- /dev/null +++ b/apps/tangle-dapp/app/liquid-staking/overview/page.tsx @@ -0,0 +1,57 @@ +import { Typography } from '@webb-tools/webb-ui-components'; +import { FC } from 'react'; + +import { GlassCard } from '../../../components'; +import LiquidStakingTokenItem from '../../../components/LiquidStaking/stakeAndUnstake/LiquidStakingTokenItem'; +import StatItem from '../../../components/LiquidStaking/StatItem'; +import { LS_PROTOCOLS } from '../../../constants/liquidStaking/constants'; + +const LiquidStakingPage: FC = () => { + return ( +
                + +
                + + Tangle Liquid Staking + + + + Get Liquid Staking Tokens (LSTs) to earn & unleash restaking on + Tangle Mainnet via delegation. + +
                + +
                + +
                +
                + +
                + + Liquid Staking Tokens + + + +
                +
                + {LS_PROTOCOLS.map((protocol) => { + return ( + + ); + })} +
                +
                +
                +
                +
                + ); +}; + +export default LiquidStakingPage; diff --git a/apps/tangle-dapp/app/liquid-staking/page.tsx b/apps/tangle-dapp/app/liquid-staking/page.tsx index f7246679a..36e0526f4 100644 --- a/apps/tangle-dapp/app/liquid-staking/page.tsx +++ b/apps/tangle-dapp/app/liquid-staking/page.tsx @@ -1,57 +1,54 @@ -import { Typography } from '@webb-tools/webb-ui-components'; +'use client'; + import { FC } from 'react'; -import { GlassCard } from '../../components'; -import LiquidStakingTokenItem from '../../components/LiquidStaking/stakeAndUnstake/LiquidStakingTokenItem'; -import StatItem from '../../components/LiquidStaking/StatItem'; -import { LS_PROTOCOLS } from '../../constants/liquidStaking/constants'; +import { LiquidStakingSelectionTable } from '../../components/LiquidStaking/LiquidStakingSelectionTable'; +import LiquidStakeCard from '../../components/LiquidStaking/stakeAndUnstake/LiquidStakeCard'; +import LiquidUnstakeCard from '../../components/LiquidStaking/stakeAndUnstake/LiquidUnstakeCard'; +import UnstakeRequestsTable from '../../components/LiquidStaking/unstakeRequestsTable/UnstakeRequestsTable'; +import { LsSearchParamKey } from '../../constants/liquidStaking/types'; +import useSearchParamState from '../../hooks/useSearchParamState'; +import TabListItem from '../restake/TabListItem'; +import TabsList from '../restake/TabsList'; + +enum SearchParamAction { + STAKE = 'stake', + UNSTAKE = 'unstake', +} + +const LiquidStakingTokenPage: FC = () => { + const [isStaking, setIsStaking] = useSearchParamState({ + defaultValue: true, + key: LsSearchParamKey.ACTION, + parser: (value) => value === SearchParamAction.STAKE, + stringify: (value) => + value ? SearchParamAction.STAKE : SearchParamAction.UNSTAKE, + }); -const LiquidStakingPage: FC = () => { return ( -
                - -
                - - Tangle Liquid Staking - - - - Get Liquid Staking Tokens (LSTs) to earn & unleash restaking on - Tangle Mainnet via delegation. - -
                - -
                - -
                -
                - -
                - - Liquid Staking Tokens - - - -
                -
                - {LS_PROTOCOLS.map((protocol) => { - return ( - - ); - })} -
                -
                -
                +
                +
                + + setIsStaking(true)}> + Stake + + + setIsStaking(false)} + > + Unstake + + + + {isStaking ? : } +
                + +
                + {isStaking ? : }
                ); }; -export default LiquidStakingPage; +export default LiquidStakingTokenPage; diff --git a/apps/tangle-dapp/components/Breadcrumbs/utils.tsx b/apps/tangle-dapp/components/Breadcrumbs/utils.tsx index 17aa3f88a..530037f37 100644 --- a/apps/tangle-dapp/components/Breadcrumbs/utils.tsx +++ b/apps/tangle-dapp/components/Breadcrumbs/utils.tsx @@ -15,7 +15,6 @@ import assert from 'assert'; import capitalize from 'lodash/capitalize'; import { JSX } from 'react'; -import { LST_PREFIX } from '../../constants/liquidStaking/constants'; import { PagePath } from '../../types'; const BREADCRUMB_ICONS: Record JSX.Element> = { @@ -29,6 +28,7 @@ const BREADCRUMB_ICONS: Record JSX.Element> = { [PagePath.RESTAKE_DELEGATE]: TokenSwapFill, [PagePath.BRIDGE]: ShuffleLine, [PagePath.LIQUID_STAKING]: WaterDropletIcon, + [PagePath.LIQUID_STAKING_OVERVIEW]: WaterDropletIcon, }; const BREADCRUMB_LABELS: Partial> = { @@ -82,15 +82,6 @@ export const getBreadcrumbLabel = ( ) { return `Details: ${pathName}`; } - // Special case for Liquid Staking individual token pages. - // Show it as something like `tgDOT` instead of `Dot`. - else if ( - pathNames.length === 2 && - index === 1 && - pathNames[0] === PagePath.LIQUID_STAKING.substring(1) - ) { - return `${LST_PREFIX}${pathName.toUpperCase()}`; - } const pathNameWithSlash = '/' + pathName; diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakingTokenItem.tsx b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakingTokenItem.tsx index 4ae4efe47..2a64c01c6 100644 --- a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakingTokenItem.tsx +++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidStakingTokenItem.tsx @@ -79,7 +79,7 @@ const LiquidStakingTokenItem: FC = ({ variant="utility" className="uppercase" rightIcon={} - href={`${PagePath.LIQUID_STAKING}/${tokenSymbol}`} + href={PagePath.LIQUID_STAKING} > Stake diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidUnstakeCard.tsx b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidUnstakeCard.tsx index 007735bc0..e62e6b65c 100644 --- a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidUnstakeCard.tsx +++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidUnstakeCard.tsx @@ -113,6 +113,7 @@ const LiquidUnstakeCard: FC = () => { return [{ address: '0x123456' as any, amount: new BN(100), decimals: 18 }]; }, []); + // TODO: Also show this for the EVM variant. // Open the request submitted modal when the redeem // transaction is complete. useEffect(() => { diff --git a/apps/tangle-dapp/components/Sidebar/sidebarProps.ts b/apps/tangle-dapp/components/Sidebar/sidebarProps.ts index 2da9981e0..1cb848197 100644 --- a/apps/tangle-dapp/components/Sidebar/sidebarProps.ts +++ b/apps/tangle-dapp/components/Sidebar/sidebarProps.ts @@ -77,7 +77,7 @@ const SIDEBAR_STATIC_ITEMS: SideBarItemProps[] = [ }, { name: 'Liquid Stake', - href: PagePath.LIQUID_STAKING, + href: PagePath.LIQUID_STAKING_OVERVIEW, environments: ['development', 'staging', 'test'], isInternal: true, isNext: true, diff --git a/apps/tangle-dapp/constants/liquidStaking/types.ts b/apps/tangle-dapp/constants/liquidStaking/types.ts index 179a58ceb..514232fed 100644 --- a/apps/tangle-dapp/constants/liquidStaking/types.ts +++ b/apps/tangle-dapp/constants/liquidStaking/types.ts @@ -6,7 +6,6 @@ import { BN } from '@polkadot/util'; import { HexString } from '@polkadot/util/types'; import { CrossChainTimeUnit } from '../../utils/CrossChainTime'; -import { StaticAssetPath } from '..'; export enum LsProtocolId { POLKADOT, diff --git a/apps/tangle-dapp/types/index.ts b/apps/tangle-dapp/types/index.ts index 32baf7af1..d28e8c40a 100755 --- a/apps/tangle-dapp/types/index.ts +++ b/apps/tangle-dapp/types/index.ts @@ -17,6 +17,7 @@ export enum PagePath { RESTAKE_DEPOSIT = '/restake/deposit', RESTAKE_DELEGATE = '/restake/delegate', LIQUID_STAKING = '/liquid-staking', + LIQUID_STAKING_OVERVIEW = '/liquid-staking/overview', } export enum QueryParamKey { From bca65a29192a6423acedcac0f9935875f69d4054 Mon Sep 17 00:00:00 2001 From: yurixander <101931215+yurixander@users.noreply.github.com> Date: Thu, 22 Aug 2024 22:43:15 -0400 Subject: [PATCH 34/60] feat(tangle-dapp): Create `useLiquifierNftUnlocks` hook --- apps/tangle-dapp/app/liquid-staking/page.tsx | 13 +- .../stakeAndUnstake/LiquidUnstakeCard.tsx | 9 +- .../unlockNftsTable/UnlockNftsTable.tsx | 270 ++++++++++++++++++ .../liquidStaking/liquifierUnlocksAbi.ts | 238 +++++++++++++++ .../data/liquifier/useContractRead.ts | 20 +- .../data/liquifier/useLiquifierNftUnlocks.ts | 136 +++++++++ 6 files changed, 675 insertions(+), 11 deletions(-) create mode 100644 apps/tangle-dapp/components/LiquidStaking/unlockNftsTable/UnlockNftsTable.tsx create mode 100644 apps/tangle-dapp/constants/liquidStaking/liquifierUnlocksAbi.ts create mode 100644 apps/tangle-dapp/data/liquifier/useLiquifierNftUnlocks.ts diff --git a/apps/tangle-dapp/app/liquid-staking/page.tsx b/apps/tangle-dapp/app/liquid-staking/page.tsx index 36e0526f4..52a63e562 100644 --- a/apps/tangle-dapp/app/liquid-staking/page.tsx +++ b/apps/tangle-dapp/app/liquid-staking/page.tsx @@ -5,9 +5,12 @@ import { FC } from 'react'; import { LiquidStakingSelectionTable } from '../../components/LiquidStaking/LiquidStakingSelectionTable'; import LiquidStakeCard from '../../components/LiquidStaking/stakeAndUnstake/LiquidStakeCard'; import LiquidUnstakeCard from '../../components/LiquidStaking/stakeAndUnstake/LiquidUnstakeCard'; +import UnlockNftsTable from '../../components/LiquidStaking/unlockNftsTable/UnlockNftsTable'; import UnstakeRequestsTable from '../../components/LiquidStaking/unstakeRequestsTable/UnstakeRequestsTable'; import { LsSearchParamKey } from '../../constants/liquidStaking/types'; +import { useLiquidStakingStore } from '../../data/liquidStaking/useLiquidStakingStore'; import useSearchParamState from '../../hooks/useSearchParamState'; +import isLsParachainChainId from '../../utils/liquidStaking/isLsParachainChainId'; import TabListItem from '../restake/TabListItem'; import TabsList from '../restake/TabsList'; @@ -25,6 +28,8 @@ const LiquidStakingTokenPage: FC = () => { value ? SearchParamAction.STAKE : SearchParamAction.UNSTAKE, }); + const { selectedProtocolId } = useLiquidStakingStore(); + return (
                @@ -45,7 +50,13 @@ const LiquidStakingTokenPage: FC = () => {
                - {isStaking ? : } + {isStaking ? ( + + ) : isLsParachainChainId(selectedProtocolId) ? ( + + ) : ( + + )}
                ); diff --git a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidUnstakeCard.tsx b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidUnstakeCard.tsx index e62e6b65c..fdc7b9368 100644 --- a/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidUnstakeCard.tsx +++ b/apps/tangle-dapp/components/LiquidStaking/stakeAndUnstake/LiquidUnstakeCard.tsx @@ -132,6 +132,11 @@ const LiquidUnstakeCard: FC = () => { /> ); + // TODO: Also check if the user has enough balance to unstake. + const canCallUnstake = + (selectedProtocol.type === 'parachain' && executeRedeemTx !== null) || + (selectedProtocol.type === 'erc20' && performLiquifierUnlock !== null); + return ( <> {/* TODO: Have a way to trigger a refresh of the amount once the wallet balance (max) button is clicked. Need to signal to the liquid staking input to update its display amount based on the `fromAmount` prop. */} @@ -183,9 +188,7 @@ const LiquidUnstakeCard: FC = () => {