Skip to content

Commit

Permalink
feat(tangle-dapp): Create LsCreatePoolModal component
Browse files Browse the repository at this point in the history
  • Loading branch information
yurixander committed Oct 12, 2024
1 parent bbfab3d commit a9261f0
Show file tree
Hide file tree
Showing 15 changed files with 309 additions and 17 deletions.
31 changes: 25 additions & 6 deletions apps/tangle-dapp/app/liquid-staking/page.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
'use client';

import {
AddLineIcon,
CoinIcon,
EditLine,
Search,
SparklingIcon,
WaterDropletIcon,
} from '@webb-tools/icons';
import {
Button,
TabContent,
TabsList as WebbTabsList,
TabsRoot,
TabTrigger,
TANGLE_DOCS_LIQUID_STAKING_URL,
Typography,
} from '@webb-tools/webb-ui-components';
import { FC, useEffect } from 'react';
import { FC, useEffect, useState } from 'react';

import LsStakeCard from '../../components/LiquidStaking/stakeAndUnstake/LsStakeCard';
import LsUnstakeCard from '../../components/LiquidStaking/stakeAndUnstake/LsUnstakeCard';
Expand All @@ -24,6 +26,7 @@ import OnboardingModal from '../../components/OnboardingModal/OnboardingModal';
import StatItem from '../../components/StatItem';
import { OnboardingPageKey } from '../../constants';
import { LsSearchParamKey } from '../../constants/liquidStaking/types';
import LsCreatePoolModal from '../../containers/LsCreatePoolModal';
import LsMyProtocolsTable from '../../containers/LsMyProtocolsTable';
import { LsProtocolsTable } from '../../containers/LsPoolsTable';
import useNetworkStore from '../../context/useNetworkStore';
Expand Down Expand Up @@ -61,6 +64,7 @@ const LiquidStakingPage: FC = () => {

const { network } = useNetworkStore();
const { switchNetwork } = useNetworkSwitcher();
const [isCreatePoolModalOpen, setIsCreatePoolModalOpen] = useState(false);

const lsTangleNetwork = getLsTangleNetwork(lsNetworkId);

Expand All @@ -87,6 +91,11 @@ const LiquidStakingPage: FC = () => {

return (
<div>
<LsCreatePoolModal
isOpen={isCreatePoolModalOpen}
setIsOpen={setIsCreatePoolModalOpen}
/>

<OnboardingModal
title="Get Started with Liquid Staking"
pageKey={OnboardingPageKey.LIQUID_STAKING}
Expand Down Expand Up @@ -141,11 +150,7 @@ const LiquidStakingPage: FC = () => {
</div>

<div className="flex gap-6 h-full">
<StatItem
title="$123.01"
subtitle="My Total Staking"
largeSubtitle
/>
<StatItem title="$123.01" subtitle="My Total Staking" />
</div>
</div>

Expand Down Expand Up @@ -192,6 +197,20 @@ const LiquidStakingPage: FC = () => {
);
})}
</WebbTabsList>

{/**
* TODO: Check what's the min. amount required to create a new pool. If the free balance doesn't meet the min, disable the button and show a tooltip with the reason.
*/}
<Button
onClick={() => setIsCreatePoolModalOpen(true)}
variant="utility"
size="sm"
rightIcon={
<AddLineIcon className="fill-current dark:fill-current" />
}
>
Create Pool
</Button>
</div>

{/* Tabs Content */}
Expand Down
3 changes: 3 additions & 0 deletions apps/tangle-dapp/components/AddressInput/AddressInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export type AddressInputProps = {
id: string;
title: string;
placeholder?: string;
tooltip?: string;
type: AddressType;
showPasteButton?: boolean;
value: string;
Expand All @@ -29,6 +30,7 @@ export type AddressInputProps = {
const AddressInput: FC<AddressInputProps> = ({
id,
title,
tooltip,
placeholder,
type,
value,
Expand Down Expand Up @@ -104,6 +106,7 @@ const AddressInput: FC<AddressInputProps> = ({
<BaseInput
id={id}
title={title}
tooltip={tooltip}
errorMessage={errorMessage ?? undefined}
{...baseInputOverrides}
actions={actions}
Expand Down
3 changes: 2 additions & 1 deletion apps/tangle-dapp/components/AmountInput/AmountInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ const AmountInput: FC<AmountInputProps> = ({
setAmount,
min = null,
max = null,
decimals = TANGLE_TOKEN_DECIMALS, // Default to the Tangle token decimals.
// Default to the Tangle token decimals.
decimals = TANGLE_TOKEN_DECIMALS,
minErrorMessage,
maxErrorMessage,
showMaxAction = true,
Expand Down
4 changes: 1 addition & 3 deletions apps/tangle-dapp/components/LiquidStaking/ExternalLink.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,7 @@ const ExternalLink: FC<ExternalLinkProps> = ({
target="_blank"
size="sm"
variant="link"
rightIcon={
<Icon className="dark:fill-blue-50 group-hover:dark:fill-blue-30" />
}
rightIcon={<Icon className="fill-current dark:fill-current" />}
>
{children}
</Button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,7 @@ const LsMyPoolsTable: FC<LsMyPoolsTableProps> = ({ pools, isShown }) => {
return (
<TableStatus
title="No active pools"
className={sharedTableStatusClxs}
description="You haven't staked in any pools yet. Select a pool and start liquid staking to earn rewards! Once you've staked or created a pool, you'll be able to manage your stake and configure the pool here."
icon="🔍"
buttonText="Learn More"
Expand Down
6 changes: 2 additions & 4 deletions apps/tangle-dapp/components/StatItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,13 @@ export type StatItemProps = {
title: string;
subtitle: string;
tooltip?: string;
largeSubtitle?: boolean;
removeBorder?: boolean;
};

const StatItem: FC<StatItemProps> = ({
title,
subtitle,
tooltip,
largeSubtitle = false,
removeBorder = false,
}) => {
const className = cx('flex flex-col items-start justify-center px-3', {
Expand All @@ -26,13 +24,13 @@ const StatItem: FC<StatItemProps> = ({

return (
<div className={className}>
<Typography className="dark:text-mono-0" variant="body2" fw="bold">
<Typography className="dark:text-mono-0" variant="body1" fw="bold">
{title}
</Typography>

<div className="flex gap-1 items-start justify-start">
<Typography
variant={largeSubtitle ? 'body1' : 'body2'}
variant="body2"
fw="normal"
className="text-mono-120 dark:text-mono-100"
>
Expand Down
1 change: 1 addition & 0 deletions apps/tangle-dapp/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export enum TxName {
LS_LIQUIFIER_WITHDRAW = 'liquifier withdraw',
LS_TANGLE_POOL_JOIN = 'join liquid staking pool',
LS_TANGLE_POOL_UNBOND = 'unbond from liquid staking pool',
LS_TANGLE_POOL_CREATE = 'create liquid staking pool',
}

export const PAYMENT_DESTINATION_OPTIONS: StakingRewardsDestinationDisplayText[] =
Expand Down
203 changes: 203 additions & 0 deletions apps/tangle-dapp/containers/LsCreatePoolModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
import { BN } from '@polkadot/util';
import { TANGLE_TOKEN_DECIMALS } from '@webb-tools/dapp-config';
import {
Alert,
Button,
Input,
Modal,
ModalContent,
ModalFooter,
ModalHeader,
TANGLE_DOCS_LS_CREATE_POOL_URL,
} from '@webb-tools/webb-ui-components';
import assert from 'assert';
import { FC, useCallback, useState } from 'react';

import AddressInput, {
AddressType,
} from '../components/AddressInput/AddressInput';
import AmountInput from '../components/AmountInput/AmountInput';
import { LsNetworkId, LsProtocolId } from '../constants/liquidStaking/types';
import useBalances from '../data/balances/useBalances';
import useLsCreatePoolTx from '../data/liquidStaking/tangle/useLsCreatePoolTx';
import { useLsStore } from '../data/liquidStaking/useLsStore';
import useInputAmount from '../hooks/useInputAmount';
import useSubstrateAddress from '../hooks/useSubstrateAddress';
import { TxStatus } from '../hooks/useSubstrateTx';
import { SubstrateAddress } from '../types/utils';
import getLsNetwork from '../utils/liquidStaking/getLsNetwork';
import getLsProtocolDef from '../utils/liquidStaking/getLsProtocolDef';

export type LsCreatePoolModalProps = {
isOpen: boolean;
setIsOpen: (isOpen: boolean) => void;
};

const LsCreatePoolModal: FC<LsCreatePoolModalProps> = ({
isOpen,
setIsOpen,
}) => {
const activeSubstrateAddress = useSubstrateAddress();
// TODO: Use form validation for the properties/inputs.
const [name, setName] = useState('');
const [rootAddress, setRootAddress] = useState(activeSubstrateAddress);
const { free: freeBalance } = useBalances();

const [nominatorAddress, setNominatorAddress] =
useState<SubstrateAddress | null>(activeSubstrateAddress);

const [bouncerAddress, setBouncerAddress] = useState<SubstrateAddress | null>(
activeSubstrateAddress,
);

const [initialBondAmount, setInitialBondAmount] = useState<BN | null>(null);
const [lsProtocolId, setLsProtocolId] = useState<LsProtocolId | null>(null);
const { lsNetworkId } = useLsStore();

const lsProtocol =
lsProtocolId === null ? null : getLsProtocolDef(lsProtocolId);

const lsNetwork = getLsNetwork(lsNetworkId);

const { displayAmount, errorMessage } = useInputAmount({
amount: initialBondAmount,
setAmount: setInitialBondAmount,
// Default to TNT's decimals if the protocol hasn't been selected
// yet.
decimals: lsProtocol?.decimals ?? TANGLE_TOKEN_DECIMALS,
});

// TODO: Also add Restaking Parachain when its non-testnet version is available.
const isLiveNetwork = lsNetworkId === LsNetworkId.TANGLE_MAINNET;

const { execute, status } = useLsCreatePoolTx();

const handleCreatePoolClick = useCallback(async () => {
// TODO: Add form validation, then remove this check.
if (
initialBondAmount === null ||
rootAddress === null ||
nominatorAddress === null ||
bouncerAddress === null
) {
return;
}

assert(
execute !== null,
'Button should have been disabled if execute is null.',
);

await execute({
name,
initialBondAmount,
rootAddress,
nominatorAddress,
bouncerAddress,
});
}, [
bouncerAddress,
execute,
initialBondAmount,
name,
nominatorAddress,
rootAddress,
]);

return (
<Modal open={isOpen}>
<ModalContent isCenter isOpen={isOpen} className="w-full max-w-[700px]">
<ModalHeader onClose={() => setIsOpen(false)}>
Create a Liquid Staking Pool
</ModalHeader>

<div className="p-9 space-y-8">
<div className="flex flex-col items-center gap-4">
{/**
* In case that a testnet is selected, it's helpful to let the users
* know that the pool will be created on the testnet, and that
* it won't be accessible on other networks.
*/}
{!isLiveNetwork && (
<Alert
type="info"
title="Note"
description={`This liquid staking pool will be created on ${lsNetwork.networkName} and will not be accessible on other networks.`}
/>
)}

<Input
id="ls-create-pool-name"
placeholder="Name"
value={name}
onChange={setName}
/>

{/** TODO: Protocol selection dropdown. */}

<AmountInput
id="ls-create-pool-initial-bond-amount"
amount={initialBondAmount}
setAmount={setInitialBondAmount}
max={freeBalance}
title="Initial Bond Amount"
wrapperClassName="w-full"
/>

<AddressInput
id="ls-create-pool-root-address"
title="Root Address"
tooltip="The root is the administrator of the pool with full control over its operations, including updating roles, and commission setup"
type={AddressType.Substrate}
value={rootAddress ?? ''}
setValue={setRootAddress}
wrapperClassName="w-full"
/>

<AddressInput
id="ls-create-pool-nominator-address"
title="Nominator Address"
tooltip="The nominator is responsible for selecting validators on behalf of the pool. Their role is critical in optimizing rewards for the pool members by choosing high-performing and secure validators."
type={AddressType.Substrate}
value={nominatorAddress ?? ''}
setValue={setNominatorAddress}
wrapperClassName="w-full"
/>

<AddressInput
id="ls-create-pool-bouncer-address"
title="Bouncer Address"
tooltip="The bouncer is responsible for managing the entry and exit of participants into the pool. They can block or allow participants, as well as manage pool access settings."
type={AddressType.Substrate}
value={nominatorAddress ?? ''}
setValue={setNominatorAddress}
wrapperClassName="w-full"
/>
</div>
</div>

<ModalFooter className="flex items-center gap-2">
<Button
isFullWidth
variant="secondary"
target="_blank"
href={TANGLE_DOCS_LS_CREATE_POOL_URL}
>
Learn More
</Button>

<Button
onClick={handleCreatePoolClick}
isLoading={execute === null || status === TxStatus.PROCESSING}
loadingText="Processing"
isFullWidth
>
Create Pool
</Button>
</ModalFooter>
</ModalContent>
</Modal>
);
};

export default LsCreatePoolModal;
4 changes: 3 additions & 1 deletion apps/tangle-dapp/containers/LsPoolsTable/LsPoolsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,9 @@ const LsPoolsTable: FC<LsPoolsTableProps> = ({ pools, isShown }) => {
isDisabled={lsPoolId === props.row.original.id}
onClick={() => setLsStakingIntent(props.row.original.id, true)}
rightIcon={
lsPoolId !== props.row.original.id ? <ArrowRight /> : undefined
lsPoolId !== props.row.original.id ? (
<ArrowRight className="fill-current dark:fill-current" />
) : undefined
}
variant="utility"
size="sm"
Expand Down
Loading

0 comments on commit a9261f0

Please sign in to comment.