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

Merge develop to main #314

Merged
merged 27 commits into from
Apr 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
948e01a
feat: updated FAQ texts, added timestamp on request items in claim page
Tarens2 Nov 17, 2023
337fe52
feat: updated request status text breakpoint
Tarens2 Nov 17, 2023
5d7592f
feat: added default opened accordion by anchor
Tarens2 Nov 20, 2023
24ea2e2
feat: fix window deps
Tarens2 Nov 21, 2023
c15eece
feat: moved faq out of nossr
Tarens2 Nov 21, 2023
a836d13
feat: fix links
Tarens2 Nov 23, 2023
0b9f20d
Merge branch 'develop' into feature/si-867-texts-for-waiting-time
Tarens2 Nov 23, 2023
3038941
fix: after merge updates
Tarens2 Nov 23, 2023
2789a41
fix: fix request info
Tarens2 Nov 24, 2023
437554e
fix: fix request info
Tarens2 Nov 24, 2023
30647ce
fix: unblock claim requests list
Tarens2 Nov 27, 2023
f7d1da9
fix: fix warning
Tarens2 Nov 27, 2023
3c63baf
fix: code style fix
Tarens2 Nov 28, 2023
9434fcb
Merge branch 'develop' into feature/si-867-texts-for-waiting-time
Tarens2 Nov 29, 2023
4640858
fix: fix anchor
Tarens2 Nov 29, 2023
c81ffd6
fix: revert faq ssr
Tarens2 Nov 29, 2023
3b7bb66
Merge branch 'develop' into feature/si-867-texts-for-waiting-time
Tarens2 Mar 18, 2024
e256b4e
Merge branch 'develop' into feature/si-867-texts-for-waiting-time
Tarens2 Mar 20, 2024
ed3c43c
fix: fetch function
Tarens2 Mar 21, 2024
d0aac08
fix: handle wrong timing
Tarens2 Mar 21, 2024
fa43f07
fix: change text, fix hover styles
Tarens2 Mar 25, 2024
cd2110f
feat: added source metric headers to wq-api requests
Tarens2 Apr 2, 2024
f6ec96d
Merge remote-tracking branch 'origin/develop' into feature/si-867-tex…
Tarens2 Apr 2, 2024
7c5d8db
Merge branch 'develop' into feature/si-867-texts-for-waiting-time
Tarens2 Apr 10, 2024
4ee6056
fix: fixed dynamic config after merge
Tarens2 Apr 10, 2024
dd6dd21
fix: fix debounce
Tarens2 Apr 10, 2024
9b88c31
Merge pull request #138 from lidofinance/feature/si-867-texts-for-wai…
jake4take Apr 10, 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
6 changes: 2 additions & 4 deletions features/stake/stake-faq/list/how-can-i-unstake-steth.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,8 @@ export const HowCanIUnstakeSteth: FC = () => {
>
Withdrawals Request and Claim tabs
</LocalLink>{' '}
to unstake stETH and receive ETH at a 1:1 ratio. Under normal
circumstances, withdrawal period can take anywhere between 1-5 days.
After that, you can claim your ETH using the Claim tab. Also, you can
exchange stETH on{' '}
to unstake stETH and receive ETH at a 1:1 ratio. Also, you can exchange
stETH on{' '}
<OuterLink
href="https://lido.fi/lido-ecosystem?tokens=stETH&categories=Get"
data-matomo={
Expand Down
Original file line number Diff line number Diff line change
@@ -1,39 +1,36 @@
import { Tooltip } from '@lidofinance/lido-ui';
import { useWaitingTime } from 'features/withdrawals/hooks/useWaitingTime';
import {
RequestsStatusStyled,
DesktopStatus,
MobileStatusIcon,
RequestInfoIcon,
} from './styles';
import { RequestsStatusStyled, StatusIcon, StatusText } from './styles';
import { forwardRef } from 'react';
import { formatTimestamp } from '../../../utils/format-timestamp';
import { useBreakpoint } from '@lidofinance/lido-ui';

type RequestItemStatusProps = { status: 'ready' | 'pending' };

export const RequestStatus: React.FC<RequestItemStatusProps> = ({ status }) => {
if (status === 'pending') return <RequestStatusPending />;
return <RequestStatusBody status="ready" />;
type RequestItemStatusProps = {
status: 'ready' | 'pending';
finalizationAt: string | null;
};

const RequestStatusPending: React.FC = () => {
const waitingTime = useWaitingTime('');
return (
<Tooltip title={`Current withdrawal period is ${waitingTime.value}.`}>
<RequestStatusBody status="pending" />
</Tooltip>
);
export const RequestStatus: React.FC<RequestItemStatusProps> = (props) => {
return <RequestStatusBody {...props} />;
};

const RequestStatusBody = forwardRef<
HTMLDivElement,
RequestItemStatusProps & React.ComponentProps<'div'>
>(({ status, ...props }, ref) => {
const statusText = status === 'ready' ? 'Ready to claim' : 'Pending';
>(({ status, finalizationAt, ...props }, ref) => {
let statusText;
const isMobile = useBreakpoint('md');

if (status.toLocaleLowerCase() === 'ready') {
statusText = 'Ready';
} else if (finalizationAt) {
statusText = formatTimestamp(finalizationAt, isMobile);
} else {
statusText = 'Pending';
}

return (
<RequestsStatusStyled {...props} ref={ref} $variant={status}>
<DesktopStatus>{statusText}</DesktopStatus>
<MobileStatusIcon $variant={status} />
{status === 'pending' && <RequestInfoIcon />}
<StatusIcon $variant={status} />
<StatusText>{statusText}</StatusText>
</RequestsStatusStyled>
);
});
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,10 @@ export const RequestItem = forwardRef<HTMLInputElement, RequestItemProps>(
name={name}
ref={ref}
/>
<RequestStatus status={status.isFinalized ? 'ready' : 'pending'} />
<RequestStatus
status={status.isFinalized ? 'ready' : 'pending'}
finalizationAt={status.finalizationAt}
/>
<LinkStyled
data-testid="requestNftLink"
href={getNFTUrl(token_id, chainId)}
Expand Down
36 changes: 6 additions & 30 deletions features/withdrawals/claim/form/requests-list/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { InlineLoader, Link, ThemeName } from '@lidofinance/lido-ui';

import RequestReady from 'assets/icons/request-ready.svg';
import RequestPending from 'assets/icons/request-pending.svg';
import RequestInfo from 'assets/icons/request-info.svg';

export const REQUESTS_LIST_ITEM_SIZE = 57;
export const REQUESTS_LIST_LOADERS_COUNT = 3;
Expand Down Expand Up @@ -60,7 +59,6 @@ export const RequestsStatusStyled = styled.div<RequestProps>`
margin-right: 8px;
padding: 2px ${({ theme }) => theme.spaceMap.sm}px;
${({ theme }) => theme.mediaQueries.sm} {
padding: 4px;
min-width: 24px;
justify-content: center;
}
Expand All @@ -74,44 +72,22 @@ export const RequestsStatusStyled = styled.div<RequestProps>`
? 'rgba(83, 186, 149, 0.16)'
: 'rgba(236, 134, 0, 0.16)'};

${({ $variant }) =>
$variant === 'pending' &&
`&:hover {
background-color: rgba(236, 134, 0, 0.26);
}`}

color: ${({ $variant }) => ($variant === 'ready' ? '#53BA95' : '#EC8600')};
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
`;

export const DesktopStatus = styled.span`
export const StatusText = styled.span`
font-size: 12px;
font-weight: 600;
${({ theme }) => theme.mediaQueries.sm} {
display: none;
}
`;

export const MobileStatusIcon = styled.img.attrs<RequestProps>(
({ $variant }) => ({
alt: $variant,
src: $variant === 'ready' ? RequestReady : RequestPending,
}),
)<RequestProps>`
display: none;
width: 16px;
height: 16px;
${({ theme }) => theme.mediaQueries.sm} {
display: block;
}
`;

export const RequestInfoIcon = styled.img.attrs({
alt: 'info',
src: RequestInfo,
})`
export const StatusIcon = styled.img.attrs<RequestProps>(({ $variant }) => ({
alt: $variant,
src: $variant === 'ready' ? RequestReady : RequestPending,
}))<RequestProps>`
display: block;
width: 16px;
height: 16px;
`;
Expand Down
85 changes: 81 additions & 4 deletions features/withdrawals/hooks/contract/useWithdrawalsData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,83 @@ import { useCallback } from 'react';
import { Zero } from '@ethersproject/constants';
import { BigNumber } from 'ethers';
import { useLidoSWR } from '@lido-sdk/react';
// import { useLidoShareRate } from 'features/withdrawals/hooks/contract/useLidoShareRate';

import { useWithdrawalsContract } from './useWithdrawalsContract';

import {
RequestStatus,
RequestStatusClaimable,
RequestStatusPending,
} from 'features/withdrawals/types/request-status';
import { MAX_SHOWN_REQUEST_PER_TYPE } from 'features/withdrawals/withdrawals-constants';
import { STRATEGY_LAZY } from 'consts/swr-strategies';
import { standardFetcher } from 'utils/standardFetcher';
import { default as dynamics } from 'config/dynamics';

// import { calcExpectedRequestEth } from 'features/withdrawals/utils/calc-expected-request-eth';
import { encodeURLQuery } from 'utils/encodeURLQuery';

export type WithdrawalRequests = NonNullable<
ReturnType<typeof useWithdrawalRequests>['data']
>;

export type RequestInfoDto = {
finalizationIn: number;
finalizationAt: string;
requestId: string;
requestedAt: string;
};

export type RequestTimeByRequestIds = {
requestInfo: RequestInfoDto;
nextCalculationAt: string;
};

const getRequestTimeForWQRequestIds = async (
ids: string[],
): Promise<
{
id: string;
finalizationAt: string;
}[]
> => {
const idsPages = [];
const pageSize = 20;

for (let i = 0; i < ids.length; i += pageSize) {
idsPages.push(ids.slice(i, i + pageSize));
}

const result = [];

for (const page of idsPages) {
const basePath = dynamics.wqAPIBasePath;
const params = encodeURLQuery({ ids: page.toString() });
const queryString = params ? `?${params}` : '';
const url = `${basePath}/v2/request-time${queryString}`;
const requests = await standardFetcher<RequestTimeByRequestIds[]>(url, {
headers: {
'Content-Type': 'application/json',
'WQ-Request-Source': 'widget',
},
});

for (const request of requests) {
if (!request || !request.requestInfo) continue;
const modifiedResult = {
id: request.requestInfo.requestId,
finalizationAt: request.requestInfo.finalizationAt,
};

result.push(modifiedResult);
}

if (idsPages.length > 1) {
// avoid backend spam
await new Promise((resolve) => setTimeout(resolve, 1000));
}
}

return result;
};

export const useWithdrawalRequests = () => {
const { contractRpc, account, chainId } = useWithdrawalsContract();
// const { data: currentShareRate } = useLidoShareRate();
Expand All @@ -42,6 +101,21 @@ export const useWithdrawalRequests = () => {

const claimableRequests: RequestStatus[] = [];
const pendingRequests: RequestStatusPending[] = [];
const pendingRequestsIds: string[] = [];

requestStatuses.forEach((request, index) => {
if (!request.isFinalized) {
pendingRequestsIds.push(requestIds[index].toString());
}
});

let wqRequests: { finalizationAt: string; id: string }[] = [];

try {
wqRequests = await getRequestTimeForWQRequestIds(pendingRequestsIds);
} catch (e) {
console.warn('Failed to fetch request time for requests ids', e);
}

let pendingAmountOfStETH = BigNumber.from(0);
let claimableAmountOfStETH = BigNumber.from(0);
Expand All @@ -52,6 +126,7 @@ export const useWithdrawalRequests = () => {
...request,
id,
stringId: id.toString(),
finalizationAt: null,
};

if (request.isFinalized && !request.isClaimed) {
Expand All @@ -60,8 +135,10 @@ export const useWithdrawalRequests = () => {
request.amountOfStETH,
);
} else if (!request.isFinalized) {
const r = wqRequests.find((r) => r.id === id.toString());
pendingRequests.push({
...req,
finalizationAt: r?.finalizationAt ?? null,
expectedEth: req.amountOfStETH, // TODO: replace with calcExpectedRequestEth(req, currentShareRate),
});
pendingAmountOfStETH = pendingAmountOfStETH.add(
Expand Down
46 changes: 27 additions & 19 deletions features/withdrawals/hooks/useWaitingTime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,18 @@ import { FetcherError } from 'utils/fetcherError';

const DEFAULT_DAYS_VALUE = 5;

type RequestTimeResponse = {
days: number;
stethLastUpdate: number;
validatorsLastUpdate: number;
steth: string;
requests: number;
};

type useWaitingTimeOptions = {
isApproximate?: boolean;
};

type RequestTimeV2Dto = {
requestInfo: {
finalizationIn: number;
finalizationAt: string;
};
nextCalculationAt: string;
};

// TODO: accept big Number
export const useWaitingTime = (
amount: string,
Expand All @@ -34,22 +34,31 @@ export const useWaitingTime = (
const basePath = config.wqAPIBasePath;
const params = encodeURLQuery({ amount: debouncedAmount });
const queryString = params ? `?${params}` : '';
return `${basePath}/v1/request-time${queryString}`;
return `${basePath}/v2/request-time/calculate${queryString}`;
}, [debouncedAmount]);

const { data, initialLoading, error } = useLidoSWR(url, standardFetcher, {
...STRATEGY_EAGER,
shouldRetryOnError: (e: unknown) => {
// if api is not happy about our request - no retry
return !(e && typeof e == 'object' && 'status' in e && e.status == 400);
const { data, initialLoading, error } = useLidoSWR(
['swr:waiting-time', debouncedAmount],
() =>
standardFetcher(url, {
headers: {
'Content-Type': 'application/json',
'WQ-Request-Source': 'widget',
},
}),
{
...STRATEGY_EAGER,
shouldRetryOnError: (e: unknown) => {
// if api is not happy about our request - no retry
return !(e && typeof e == 'object' && 'status' in e && e.status == 400);
},
},
}) as SWRResponse<RequestTimeResponse>;
) as SWRResponse<RequestTimeV2Dto>;
const { isBunker, isPaused } = useWithdrawals();
const isRequestError = error instanceof FetcherError && error.status < 500;

const stethLastUpdate =
data?.stethLastUpdate && new Date(data?.stethLastUpdate * 1000);
const days = data?.days ?? DEFAULT_DAYS_VALUE;
const ms = data?.requestInfo?.finalizationIn;
const days = ms ? Math.ceil(ms / (24 * 60 * 60 * 1000)) : DEFAULT_DAYS_VALUE;

const waitingTime =
days && days > 1
Expand All @@ -62,7 +71,6 @@ export const useWaitingTime = (
...data,
initialLoading,
error,
stethLastUpdate,
days,
value,
};
Expand Down
1 change: 1 addition & 0 deletions features/withdrawals/types/request-status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export type RequestStatus = {
isClaimed: boolean;
id: BigNumber;
stringId: string;
finalizationAt: string | null;
};

export type RequestStatusClaimable = RequestStatus & {
Expand Down
20 changes: 20 additions & 0 deletions features/withdrawals/utils/format-timestamp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
export const formatTimestamp = (timestamp: string, short = false): string => {
const diff = new Date(timestamp).getTime() - Date.now();

if (diff < 0) {
return 'Pending';
}

const hours = Math.ceil(diff / (1000 * 60 * 60));
const hour = short ? 'h' : 'hour';

if (hours < 24) {
const plural = hours === 1 || short ? '' : 's';
return `~${hours} ${hour}${plural}`;
}

const days = Math.ceil(hours / 24);
const day = short ? 'd' : 'day';
const plural = days === 1 || short ? '' : 's';
return `~${days} ${day}${plural}`;
};
4 changes: 4 additions & 0 deletions features/withdrawals/withdrawals-constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ export const MAX_REQUESTS_COUNT_LEDGER_LIMIT = 2;
export const DEFAULT_CLAIM_REQUEST_SELECTED = 80;
export const MAX_SHOWN_REQUEST_PER_TYPE = 1024;

export const WITHDRAWAL_PERIOD_PATH = '#withdrawalsPeriod';
export const WHAT_IS_BUNKER = '#whatIsBunkerMode';
export const WHAT_IS_TURBO = '#whatIsTurboMode';

// time that validation function waits for context data to resolve
// should be enough to load token balances/tvl/max&min amounts and other contract data
export const VALIDATION_CONTEXT_TIMEOUT = 4000;
Expand Down
Loading
Loading