Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(tangle-dapp): Implement Liquifier unlock NFTs unstake requests table #2521

Merged
merged 68 commits into from
Aug 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
be22ef0
refactor(tangle-dapp): Rename things to accommodate for EVM LS
yurixander Aug 4, 2024
67e9929
feat(tangle-dapp): Add some notes on flow
yurixander Aug 9, 2024
237d34b
feat(tangle-dapp): Create `useErc20Read` hook
yurixander Aug 12, 2024
12a2ff5
Merge branch 'develop' into yuri/implement-liquifier-frontend
yurixander Aug 12, 2024
474be8b
feat(tangle-dapp): Create `useLiquifierStake` hook
yurixander Aug 13, 2024
f8a97c7
feat(tangle-dapp): Continue implementation of `useLiquifierDeposit`
yurixander Aug 13, 2024
3f61d65
feat(tangle-dapp): Add liquifier ABI
yurixander Aug 13, 2024
dfaaf21
refactor(tangle-dapp): Use `useCallback`
yurixander Aug 13, 2024
82c7f10
Merge branch 'develop' into yuri/implement-liquifier-frontend
yurixander Aug 13, 2024
9326d5e
fix(tangle-dapp): Fix merge resolution leftovers
yurixander Aug 13, 2024
7a563cd
refactor(tangle-dapp): Organize files
yurixander Aug 13, 2024
6f16172
feat(tangle-dapp): Implement `useLiquifierTgBalance` hook
yurixander Aug 13, 2024
f857b6c
refactor(tangle-dapp): Integrate ERC20 token defs with parachain chai…
yurixander Aug 14, 2024
5ae2426
refactor(tangle-dapp): Rename `chainId` to `protocolId`
yurixander Aug 14, 2024
a8fe2eb
refactor(tangle-dapp): Naming
yurixander Aug 14, 2024
f041c9b
feat(tangle-dapp): Implement `AgnosticLsBalance` and `useAgnosticLsBa…
yurixander Aug 17, 2024
3368253
feat(tangle-dapp): Create `usePolling` hook, progress on `useExchange…
yurixander Aug 18, 2024
d44f2e2
feat(tangle-dapp): Create `useContractReadSubscription` hook
yurixander Aug 18, 2024
8fc0548
Merge branch 'develop' into yuri/implement-liquifier-frontend
yurixander Aug 20, 2024
c5204f9
fix(tangle-dapp): Merge leftovers
yurixander Aug 20, 2024
1292ff9
Merge branch 'develop' into yuri/implement-liquifier-frontend
yurixander Aug 20, 2024
e09c869
fix(tangle-dapp): Solve most errors, create `CrossChainTime` class
yurixander Aug 20, 2024
25a04bf
refactor(tangle-dapp): Prefer static parachain chain time units
yurixander Aug 20, 2024
8a03be5
fix(tangle-dapp): Fix remaining errors and warnings
yurixander Aug 20, 2024
bccf21a
fix(tangle-dapp, webb-ui): Fix warnings
yurixander Aug 21, 2024
5558d50
fix(tangle-dapp): Revert removal of `'use client'`
yurixander Aug 21, 2024
f3dd7c2
fix(tangle-dapp): Fix cyclic imports
yurixander Aug 21, 2024
83923bb
fix(tangle-dapp): Improve unlock period estimate display
yurixander Aug 21, 2024
1942688
feat(tangle-dapp): Change network name when appropriate
yurixander Aug 21, 2024
b52311b
feat(tangle-dapp): Also change chain icon
yurixander Aug 21, 2024
fc05542
feat(tangle-dapp): Use Sepolia dummy smart contracts in development mode
yurixander Aug 21, 2024
481478a
refactor(tangle-dapp): Organize development contract addresses
yurixander Aug 21, 2024
11664aa
ci(tangle-dapp): Fix lint issue
yurixander Aug 21, 2024
e0484fc
Merge branch 'develop' into yuri/implement-liquifier-frontend
yurixander Aug 22, 2024
96ffa3d
refactor(tangle-dapp): Expand `useContract` into `useContractWrite` a…
yurixander Aug 22, 2024
06a44cb
feat(tangle-dapp): Add missing logos
yurixander Aug 22, 2024
628f23a
feat(tangle-dapp): Add step progress to tx notification
yurixander Aug 22, 2024
fd8d639
refactor(tangle-dapp): Cleanup liquid staking URL structure
yurixander Aug 23, 2024
bca65a2
feat(tangle-dapp): Create `useLiquifierNftUnlocks` hook
yurixander Aug 23, 2024
0c55350
fix(tangle-dapp): Fix bugs
yurixander Aug 23, 2024
bcd9ae2
feat(tangle-dapp): Use polling for ERC20 balance
yurixander Aug 23, 2024
fe522c7
ci(tangle-dapp): Fix build errors
yurixander Aug 23, 2024
d8b0f0b
refactor(tangle-dapp): Remove unused hooks
yurixander Aug 23, 2024
c16aa43
refactor(tangle-dapp): Start adapting unstake requests table to suppo…
yurixander Aug 24, 2024
3c21add
Merge branch 'develop' into yuri/liquifier-unlock-nfts-table
yurixander Aug 24, 2024
dd3220c
feat(tangle-dapp): Fetch all unlock NFTs
yurixander Aug 24, 2024
bfe3cd1
feat(tangle-dapp): List unlock NFTs, add withdraw functionality
yurixander Aug 25, 2024
84a52ce
refactor(tangle-dapp): Fetch all NFTs at once
yurixander Aug 25, 2024
1d30caf
fix(tangle-dapp): Hide withdraw button
yurixander Aug 25, 2024
25bb138
fix(tangle-dapp): Fix height/sizing
yurixander Aug 25, 2024
ff6d59f
feat(tangle-dapp): Progress on exchange rate calculation
yurixander Aug 26, 2024
e4426a0
feat(tangle-dapp): Implement `useLsFeePermill` hook
yurixander Aug 26, 2024
b2bbb5e
style(tangle-dapp): Add refreshing animation to inputs
yurixander Aug 26, 2024
b8e3936
fix(tangle-dapp): Fix total error chip not showing
yurixander Aug 26, 2024
81d0452
feat(tangle-dapp): Improve debugging performance metrics
yurixander Aug 26, 2024
bbaa759
perf(tangle-dapp): Decrease request count by using multicall
yurixander Aug 26, 2024
d9f1704
Merge branch 'develop' into yuri/liquifier-unlock-nfts-table
yurixander Aug 27, 2024
ddf4a00
refactor(tangle-dapp): Start refactoring process
yurixander Aug 27, 2024
6d63b4e
refactor(tangle-dapp): Progress on LS adapters
yurixander Aug 27, 2024
b3012f6
refactor(tangle-dapp): Organize protocols
yurixander Aug 28, 2024
38a4cc7
refactor(tangle-dapp): Progress on updating LS UI structure
yurixander Aug 28, 2024
a6e5061
fix(tangle-dapp): Fix most errors
yurixander Aug 29, 2024
d0681d4
refactor(tangle-dapp): Better naming
yurixander Aug 29, 2024
224e11a
Merge branch 'develop' into yuri/liquifier-unlock-nfts-table
yurixander Aug 29, 2024
7e88144
fix(tangle-dapp): Fix loading and refreshing states
yurixander Aug 29, 2024
0a5842c
feat(tangle-dapp): Show warning on high fees (>=10%)
yurixander Aug 29, 2024
dee1d48
fix(tangle-dapp): Fix bugs with input amounts
yurixander Aug 29, 2024
f5c80de
ci(tangle-dapp): Fix lint errors
yurixander Aug 29, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 5 additions & 16 deletions apps/tangle-dapp/app/liquid-staking/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,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 { LsValidatorTable } from '../../components/LiquidStaking/LsValidatorTable';
import LsStakeCard from '../../components/LiquidStaking/stakeAndUnstake/LsStakeCard';
import LsUnstakeCard from '../../components/LiquidStaking/stakeAndUnstake/LsUnstakeCard';
import UnstakeRequestsTable from '../../components/LiquidStaking/unstakeRequestsTable/UnstakeRequestsTable';
import { 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';

Expand All @@ -28,8 +25,6 @@ const LiquidStakingTokenPage: FC = () => {
value ? SearchParamAction.STAKE : SearchParamAction.UNSTAKE,
});

const { selectedProtocolId } = useLiquidStakingStore();

return (
<div className="flex flex-wrap gap-12">
<div className="flex flex-col gap-4 w-full min-w-[450px] max-w-[600px]">
Expand All @@ -46,17 +41,11 @@ const LiquidStakingTokenPage: FC = () => {
</TabListItem>
</TabsList>

{isStaking ? <LiquidStakeCard /> : <LiquidUnstakeCard />}
{isStaking ? <LsStakeCard /> : <LsUnstakeCard />}
</div>

<div className="flex flex-col flex-grow w-min gap-4 min-w-[370px]">
{isStaking ? (
<LiquidStakingSelectionTable />
) : isLsParachainChainId(selectedProtocolId) ? (
<UnstakeRequestsTable />
) : (
<UnlockNftsTable tokenId={selectedProtocolId} />
)}
{isStaking ? <LsValidatorTable /> : <UnstakeRequestsTable />}
</div>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const ExternalLink: FC<ExternalLinkProps> = ({
<Button
className="group"
href={href}
target="_blank"
size="sm"
variant="link"
rightIcon={
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ import {
} from '@webb-tools/webb-ui-components';
import { useEffect, useMemo, useRef, useState } from 'react';

import useLiquidStakingItems from '../../data/liquidStaking/useLiquidStakingItems';
import { useLiquidStakingStore } from '../../data/liquidStaking/useLiquidStakingStore';
import { useLiquidStakingSelectionTableColumns } from '../../hooks/LiquidStaking/useLiquidStakingSelectionTableColumns';
import { useLsStore } from '../../data/liquidStaking/useLsStore';
import useLsValidators from '../../data/liquidStaking/useLsValidators';
import { useLsValidatorSelectionTableColumns } from '../../data/liquidStaking/useLsValidatorSelectionTableColumns';
import {
LiquidStakingItem,
LiquidStakingItemType,
Expand All @@ -42,20 +42,16 @@ const SELECTED_ITEMS_COLUMN_SORT = {
desc: false,
} as const satisfies ColumnSort;

export const LiquidStakingSelectionTable = () => {
const selectedChainId = useLiquidStakingStore(
(state) => state.selectedProtocolId,
);
const setSelectedItems = useLiquidStakingStore(
(state) => state.setSelectedItems,
);
const { isLoading, data, dataType } = useLiquidStakingItems(selectedChainId);

export const LsValidatorTable = () => {
const { selectedProtocolId, setSelectedItems } = useLsStore();
const { isLoading, data, dataType } = useLsValidators(selectedProtocolId);
const [searchValue, setSearchValue] = useState('');
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});

const [sorting, setSorting] = useState<SortingState>([
SELECTED_ITEMS_COLUMN_SORT,
]);

const [pagination, setPagination] =
useState<PaginationState>(DEFAULT_PAGINATION);

Expand All @@ -67,7 +63,7 @@ export const LiquidStakingSelectionTable = () => {
setSelectedItems(new Set(Object.keys(rowSelection)));
}, [rowSelection, setSelectedItems]);

const columns = useLiquidStakingSelectionTableColumns(
const columns = useLsValidatorSelectionTableColumns(
toggleSortSelectionHandlerRef,
dataType,
) as ColumnDef<LiquidStakingItemType, unknown>[];
Expand Down Expand Up @@ -99,15 +95,18 @@ export const LiquidStakingSelectionTable = () => {
}, [dataType]);

const tableData = useMemo(() => (isLoading ? [] : data), [data, isLoading]);

const tableColumns = useMemo(
() => (isLoading ? [] : columns),
[columns, isLoading],
);

const tableIsLoading = useMemo(() => {
return (
(data.length > 0 && data[0].itemType !== dataType) || isLoading === true
);
if (isLoading) {
return true;
}

return data.length > 0 && data[0].itemType !== dataType;
}, [data, dataType, isLoading]);

const tableProps = useMemo<TableOptions<LiquidStakingItemType>>(
Expand Down Expand Up @@ -136,6 +135,7 @@ export const LiquidStakingSelectionTable = () => {
typeof updaterOrValue === 'function'
? updaterOrValue(prev)
: updaterOrValue;

return newSorting.length === 0
? [SELECTED_ITEMS_COLUMN_SORT]
: newSorting[0].id === 'id'
Expand Down Expand Up @@ -183,11 +183,11 @@ export const LiquidStakingSelectionTable = () => {
</Typography>

<Input
id="search"
id="ls-validator-selection-search"
rightIcon={<Search className="mr-2" />}
placeholder="Search"
value={searchValue}
onChange={(val) => setSearchValue(val)}
onChange={(newSearchValue) => setSearchValue(newSearchValue)}
className="mb-1"
debounceTime={300}
/>
Expand Down Expand Up @@ -224,6 +224,7 @@ export const LiquidStakingSelectionTable = () => {
<div className="flex justify-center items-center min-h-[600px]">
<div className="flex items-center justify-center gap-1">
<Spinner size="md" />

<Typography
variant="body1"
fw="normal"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
import { InformationLine } from '@webb-tools/icons';
import { IconWithTooltip, Typography } from '@webb-tools/webb-ui-components';
import {
Chip,
GITHUB_BUG_REPORT_URL,
IconWithTooltip,
InfoIconWithTooltip,
SkeletonLoader,
Typography,
} from '@webb-tools/webb-ui-components';
import { FC, ReactNode } from 'react';

import ExternalLink from '../ExternalLink';

type DetailItemProps = {
title: string;
tooltip?: string;
value: ReactNode | string;
value: Error | ReactNode | string | null;
};

const DetailItem: FC<DetailItemProps> = ({ title, tooltip, value }) => {
Expand Down Expand Up @@ -33,11 +42,38 @@ const DetailItem: FC<DetailItemProps> = ({ title, tooltip, value }) => {
<Typography className="dark:text-mono-0" variant="body1" fw="bold">
{value}
</Typography>
) : value === null ? (
<SkeletonLoader className="max-w-[80px]" />
) : value instanceof Error ? (
<ErrorChip error={value} />
) : (
value
)}
</div>
);
};

/** @internal */
const ErrorChip: FC<{ error: Error }> = ({ error }) => {
return (
<Chip color="red">
Error{' '}
<InfoIconWithTooltip
className="dark:fill-red-400 fill-red-400"
content={
<>
<Typography variant="body1">
{error.name}: {error.message}
</Typography>

<ExternalLink href={GITHUB_BUG_REPORT_URL}>
Report issue
</ExternalLink>
</>
}
/>
</Chip>
);
};

export default DetailItem;
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { SkeletonLoader } from '@webb-tools/webb-ui-components';
import { FC } from 'react';
import { twMerge } from 'tailwind-merge';

import { LST_PREFIX } from '../../../constants/liquidStaking/constants';
import { LS_DERIVATIVE_TOKEN_PREFIX } from '../../../constants/liquidStaking/constants';
import { LsProtocolId, LsToken } from '../../../constants/liquidStaking/types';
import { ExchangeRateType } from '../../../data/liquidStaking/useExchangeRate';
import useExchangeRate from '../../../data/liquidStaking/useExchangeRate';
import { ExchangeRateType } from '../../../data/liquidStaking/useLsExchangeRate';
import useLsExchangeRate from '../../../data/liquidStaking/useLsExchangeRate';
import DetailItem from './DetailItem';

export type ExchangeRateDetailItemProps = {
Expand All @@ -18,28 +19,33 @@ const ExchangeRateDetailItem: FC<ExchangeRateDetailItemProps> = ({
token,
protocolId,
}) => {
const { exchangeRate, isRefreshing } = useExchangeRate(type, protocolId);
const { exchangeRate, isRefreshing } = useLsExchangeRate(type, protocolId);

const exchangeRateElement =
exchangeRate === null ? (
exchangeRate instanceof Error ? (
exchangeRate
) : exchangeRate === null ? (
<SkeletonLoader className="w-[50px]" />
) : isRefreshing ? (
<div className="animate-pulse">{exchangeRate}</div>
) : (
exchangeRate
);

return (
<DetailItem
title="Rate"
value={
<div className="flex gap-1 items-center justify-center whitespace-nowrap">
1 {token} = {exchangeRateElement} {LST_PREFIX}
{token}
</div>
}
/>
);
const value =
exchangeRateElement instanceof Error ? (
exchangeRateElement
) : (
<div
className={twMerge(
'flex gap-1 items-center justify-center whitespace-nowrap',
isRefreshing && 'animate-pulse',
)}
>
1 {token} = {exchangeRateElement} {LS_DERIVATIVE_TOKEN_PREFIX}
{token}
</div>
);

return <DetailItem title="Rate" value={value} />;
};

export default ExchangeRateDetailItem;
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { BN, BN_ZERO } from '@polkadot/util';
import { FC, useMemo } from 'react';

import { LsProtocolId } from '../../../constants/liquidStaking/types';
import formatBn from '../../../utils/formatBn';
import getLsProtocolDef from '../../../utils/liquidStaking/getLsProtocolDef';
import scaleAmountByPermill from '../../../utils/scaleAmountByPermill';
import DetailItem from './DetailItem';
import useLsFeePermill from './useLsFeePermill';

export type FeeDetailItemProps = {
isMinting: boolean;
inputAmount: BN | null;
protocolId: LsProtocolId;
};

const FeeDetailItem: FC<FeeDetailItemProps> = ({
isMinting,
inputAmount,
protocolId,
}) => {
const feePermill = useLsFeePermill(protocolId, isMinting);

const protocol = getLsProtocolDef(protocolId);

// TODO: Add liquifier fees, and select either parachain or liquifier fees based on the given protocol's id.

const feeAmount = useMemo(() => {
// Propagate error or loading state.
if (typeof feePermill !== 'number') {
return feePermill;
}

return scaleAmountByPermill(inputAmount ?? BN_ZERO, feePermill);
}, [feePermill, inputAmount]);

const formattedFeeAmount = useMemo(() => {
// Propagate error or loading state.
if (!(feeAmount instanceof BN)) {
return feeAmount;
}

const formattedAmount = formatBn(feeAmount, protocol.decimals, {
includeCommas: true,
});

return `${formattedAmount} ${protocol.token}`;
}, [feeAmount, protocol.decimals, protocol.token]);

const feeTitle =
typeof feePermill !== 'number'
? 'Fee'
: `Fee (${(feePermill * 100).toFixed(2)}%)`;

return <DetailItem title={feeTitle} value={formattedFeeAmount} />;
};

export default FeeDetailItem;
Loading
Loading