From d427267a05f75768ec51b0c5ac8dd82c244bffa9 Mon Sep 17 00:00:00 2001 From: Nikita Kozlov Date: Thu, 18 May 2023 18:43:35 +0200 Subject: [PATCH 01/25] fix: claim for small amoutns --- features/claim/claimForm.tsx | 12 ++++++++---- features/vesting/hooks.ts | 8 +++----- features/vesting/vestingCardDetailed.tsx | 8 ++++++-- shared/lib/formatBalance.ts | 13 +++++++++++-- 4 files changed, 28 insertions(+), 13 deletions(-) diff --git a/features/claim/claimForm.tsx b/features/claim/claimForm.tsx index 0a10f8c..14df979 100644 --- a/features/claim/claimForm.tsx +++ b/features/claim/claimForm.tsx @@ -13,9 +13,10 @@ import { EtherscanLink, } from 'shared/ui'; import { useForm } from 'react-hook-form'; -import { BigNumber } from 'ethers'; +import { BigNumber, utils } from 'ethers'; import { FormControls } from './claimFormStyles'; -import { formatBalance } from 'shared/lib'; + +const { formatEther, parseEther } = utils; type ClaimFormData = { amount: string; @@ -63,7 +64,7 @@ export const ClaimForm: FC = () => { const handleClaim = useCallback( async (data: ClaimFormData) => { const { amount, address } = data; - await claim(amount, address); + await claim(parseEther(amount), address); resetCache(); }, [claim, resetCache], @@ -79,7 +80,10 @@ export const ClaimForm: FC = () => { }, [setValue, account, setShowCustomAddress]); const handleMaxClick = useCallback(() => { - setValue('amount', formatBalance(unclaimedSWR.data), { + if (unclaimedSWR.data == null) { + return; + } + setValue('amount', formatEther(unclaimedSWR.data), { shouldDirty: true, shouldValidate: true, }); diff --git a/features/vesting/hooks.ts b/features/vesting/hooks.ts index 74236c0..334ee00 100644 --- a/features/vesting/hooks.ts +++ b/features/vesting/hooks.ts @@ -1,6 +1,6 @@ import { useCallback, useMemo } from 'react'; import { useContractSWR } from '@lido-sdk/react'; -import { BigNumber, utils } from 'ethers'; +import { BigNumber } from 'ethers'; import { transaction } from 'shared/ui/transaction'; import { getTokenByAddress } from 'config'; import { useWeb3 } from 'reef-knot'; @@ -16,8 +16,6 @@ import { VestingEscrow__factory } from 'generated'; import { createContractGetter } from '@lido-sdk/contracts'; import { useVestingsContext } from './vestingsContext'; -const { parseEther } = utils; - const EVENTS_STARTING_BLOCK: Record = { [CHAINS.Mainnet]: 14441666, }; @@ -229,11 +227,11 @@ export const useVestingClaim = (escrow: string | undefined) => { const { contractWeb3 } = useVestingEscrowContract(escrow); return useCallback( - async (amount: string, account: string) => { + async (amount: BigNumber, account: string) => { if (!contractWeb3 || !chainId) return; await transaction('Claim', chainId, () => - contractWeb3['claim(address,uint256)'](account, parseEther(amount)), + contractWeb3['claim(address,uint256)'](account, amount), ); }, [chainId, contractWeb3], diff --git a/features/vesting/vestingCardDetailed.tsx b/features/vesting/vestingCardDetailed.tsx index 8dd7958..0731723 100644 --- a/features/vesting/vestingCardDetailed.tsx +++ b/features/vesting/vestingCardDetailed.tsx @@ -71,7 +71,11 @@ export const VestingCardDetailed: FC = memo( {unclaimedIsLoading || tokenIsLoading ? ( ) : ( - + )} @@ -82,7 +86,7 @@ export const VestingCardDetailed: FC = memo( {lockedIsLoading || tokenIsLoading ? ( ) : ( - + )} diff --git a/shared/lib/formatBalance.ts b/shared/lib/formatBalance.ts index f25bfd9..564eeec 100644 --- a/shared/lib/formatBalance.ts +++ b/shared/lib/formatBalance.ts @@ -9,11 +9,20 @@ export const formatBalance: FormatBalance = ( balance = Zero, maxDecimalDigits = 4, ) => { + if (balance.isZero()) { + return '0.0'; + } const balanceString = formatEther(balance); if (balanceString.includes('.')) { - const parts = balanceString.split('.'); - return parts[0] + '.' + parts[1].slice(0, maxDecimalDigits); + const [decimalPart, fractionPart] = balanceString.split('.'); + const nonZeroIndex = + fractionPart.split('').findIndex((char) => char !== '0') + 1; + return ( + decimalPart + + '.' + + fractionPart.slice(0, Math.max(nonZeroIndex, maxDecimalDigits)) + ); } return balanceString; From a4a5d98cc0509445512e3934972e6cb1ba5312eb Mon Sep 17 00:00:00 2001 From: Nikita Kozlov Date: Fri, 19 May 2023 10:58:55 +0200 Subject: [PATCH 02/25] feat: moved prefix from formatToken to formatBalance --- features/vesting/vestingCardDetailed.tsx | 2 +- shared/lib/formatBalance.ts | 14 ++++++-------- shared/ui/formatToken/formatToken.tsx | 16 ++++++++++------ 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/features/vesting/vestingCardDetailed.tsx b/features/vesting/vestingCardDetailed.tsx index 0731723..b907170 100644 --- a/features/vesting/vestingCardDetailed.tsx +++ b/features/vesting/vestingCardDetailed.tsx @@ -86,7 +86,7 @@ export const VestingCardDetailed: FC = memo( {lockedIsLoading || tokenIsLoading ? ( ) : ( - + )} diff --git a/shared/lib/formatBalance.ts b/shared/lib/formatBalance.ts index 564eeec..1c2207e 100644 --- a/shared/lib/formatBalance.ts +++ b/shared/lib/formatBalance.ts @@ -9,20 +9,18 @@ export const formatBalance: FormatBalance = ( balance = Zero, maxDecimalDigits = 4, ) => { - if (balance.isZero()) { - return '0.0'; - } const balanceString = formatEther(balance); if (balanceString.includes('.')) { const [decimalPart, fractionPart] = balanceString.split('.'); - const nonZeroIndex = + const firstFractionDigit = fractionPart.split('').findIndex((char) => char !== '0') + 1; - return ( - decimalPart + - '.' + - fractionPart.slice(0, Math.max(nonZeroIndex, maxDecimalDigits)) + const prefix = fractionPart.length > maxDecimalDigits ? '≈' : ''; + const formattedFractionPart = fractionPart.slice( + 0, + Math.max(firstFractionDigit, maxDecimalDigits), ); + return `${prefix}${decimalPart}.${formattedFractionPart}`; } return balanceString; diff --git a/shared/ui/formatToken/formatToken.tsx b/shared/ui/formatToken/formatToken.tsx index 47247a7..58e0a4c 100644 --- a/shared/ui/formatToken/formatToken.tsx +++ b/shared/ui/formatToken/formatToken.tsx @@ -1,20 +1,24 @@ import { formatBalance } from 'shared/lib'; import { ComponentProps, FC } from 'react'; import { BigNumber } from 'ethers'; +import { formatEther } from 'ethers/lib/utils'; export type FormatTokenProps = ComponentProps<'span'> & { symbol?: string; amount?: BigNumber; - approx?: boolean; }; -export const FormatToken: FC = (props) => { - const { amount, symbol, approx = false, ...rest } = props; - const prefix = !approx || amount?.isZero() ? '' : '≈'; +export const FormatToken: FC = ({ + amount, + symbol, + ...rest +}) => { + if (amount == null) { + return null; + } return ( - - {prefix} + {formatBalance(amount)} {symbol} ); From 64a7809a111bf2b126551dd8fcb702c00c90b8b7 Mon Sep 17 00:00:00 2001 From: Nikita Kozlov Date: Fri, 19 May 2023 11:15:18 +0200 Subject: [PATCH 03/25] fix: types --- features/claim/wallet.tsx | 4 ++-- features/vesting/vestingCardDetailed.tsx | 6 +----- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/features/claim/wallet.tsx b/features/claim/wallet.tsx index 430898f..4e637f9 100644 --- a/features/claim/wallet.tsx +++ b/features/claim/wallet.tsx @@ -81,7 +81,7 @@ export const Wallet: FC = () => { {unclaimedAmountUsdLoading ? ( ) : ( - + )} @@ -110,7 +110,7 @@ export const Wallet: FC = () => { {lockedAmountUsdLoading ? ( ) : ( - + )} diff --git a/features/vesting/vestingCardDetailed.tsx b/features/vesting/vestingCardDetailed.tsx index b907170..8dd7958 100644 --- a/features/vesting/vestingCardDetailed.tsx +++ b/features/vesting/vestingCardDetailed.tsx @@ -71,11 +71,7 @@ export const VestingCardDetailed: FC = memo( {unclaimedIsLoading || tokenIsLoading ? ( ) : ( - + )} From 69cd5e57c6235b24ad33ee60336b249e7878e552 Mon Sep 17 00:00:00 2001 From: Nikita Kozlov Date: Fri, 19 May 2023 12:59:37 +0200 Subject: [PATCH 04/25] fix: clear custom address --- features/claim/claimForm.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/features/claim/claimForm.tsx b/features/claim/claimForm.tsx index 0a10f8c..ce65278 100644 --- a/features/claim/claimForm.tsx +++ b/features/claim/claimForm.tsx @@ -70,13 +70,14 @@ export const ClaimForm: FC = () => { ); const handleUseCustomAddress = useCallback(() => { + setValue('address', ''); setShowCustomAddress(true); - }, [setShowCustomAddress]); + }, [setValue]); const handleUseMyAddress = useCallback(() => { setValue('address', account ?? '', { shouldValidate: true }); setShowCustomAddress(false); - }, [setValue, account, setShowCustomAddress]); + }, [setValue, account]); const handleMaxClick = useCallback(() => { setValue('amount', formatBalance(unclaimedSWR.data), { From 146e772b828ad61d7c6878d6e23c146a93a28959 Mon Sep 17 00:00:00 2001 From: Nikita Kozlov Date: Fri, 19 May 2023 13:11:44 +0200 Subject: [PATCH 05/25] fix: clear claim amount on vesting change --- features/claim/claimForm.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/features/claim/claimForm.tsx b/features/claim/claimForm.tsx index 0a10f8c..a76a219 100644 --- a/features/claim/claimForm.tsx +++ b/features/claim/claimForm.tsx @@ -27,7 +27,6 @@ export const ClaimForm: FC = () => { register, handleSubmit, setValue, - trigger, formState: { isDirty, isValid, errors }, } = useForm({ mode: 'onChange' }); @@ -47,9 +46,9 @@ export const ClaimForm: FC = () => { // Validate form if vestings changes useEffect(() => { if (isDirty) { - trigger(); + setValue('amount', ''); } - }, [isDirty, trigger, activeVesting]); + }, [setValue, isDirty, activeVesting]); const validateAmount = useCallback( (data: string) => From dac3f10752e7372a2d1c7fda6cf1ad8abb80f225 Mon Sep 17 00:00:00 2001 From: Nikita Kozlov Date: Fri, 19 May 2023 13:32:35 +0200 Subject: [PATCH 06/25] feat: disable ended programs --- features/admin/vestingRow.tsx | 42 ++++++++++++++++++++++++++--------- pages/admin.tsx | 1 + 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/features/admin/vestingRow.tsx b/features/admin/vestingRow.tsx index 3bc25f6..af2c082 100644 --- a/features/admin/vestingRow.tsx +++ b/features/admin/vestingRow.tsx @@ -1,27 +1,38 @@ import { Button, Loader, Td, Tr } from '@lidofinance/lido-ui'; -import { useRevokeUnvested, useVestingIsRevoked } from 'features/vesting'; +import { + useRevokeUnvested, + useVestingIsRevoked, + useVestingLocked, +} from 'features/vesting'; import { Vesting } from 'features/vesting/types'; import { FC, memo, MouseEventHandler } from 'react'; type StatusProps = { - data?: boolean; + isRevoked?: boolean; + isEnded?: boolean; isLoading: boolean; }; -const Status: FC = ({ data, isLoading }) => { +const Status: FC = ({ isRevoked, isEnded, isLoading }) => { if (isLoading) { return ; } - return data ? <>Revoked : <>Active; + if (isEnded) { + return <>Ended; + } + if (isRevoked) { + return <>Revoked; + } + return <>Active; }; type ActionProps = { - data?: boolean; + disabled?: boolean; isLoading: boolean; onClick?: MouseEventHandler; }; -const Action: FC = ({ data, isLoading, onClick }) => { +const Action: FC = ({ disabled, isLoading, onClick }) => { if (isLoading) { return ; } @@ -31,7 +42,7 @@ const Action: FC = ({ data, isLoading, onClick }) => { size="xxs" color="error" variant="outlined" - disabled={data} + disabled={disabled} > Revoke @@ -44,19 +55,28 @@ export type VestingRowProps = { export const VestingRow: FC = memo(({ vesting }) => { const revokeUnvested = useRevokeUnvested(vesting.escrow); - const isRevokedSWR = useVestingIsRevoked(vesting.escrow); + const { data: isRevoked, isLoading: isRevokedLoading } = useVestingIsRevoked( + vesting.escrow, + ); + const { data: locked, isLoading: isLockedLoading } = useVestingLocked( + vesting.escrow, + ); return ( {vesting.escrow} {vesting.recipient} - + diff --git a/pages/admin.tsx b/pages/admin.tsx index bc282dc..9a5600b 100644 --- a/pages/admin.tsx +++ b/pages/admin.tsx @@ -10,6 +10,7 @@ import { H3 } from '@lidofinance/lido-ui'; const AdminPage: FC = () => { const router = useRouter(); const isAdmin = useIsAdmin(); + useEffect(() => { if (isAdmin === false) { router.push('/'); From 498a487ebda35f24f851afa796362bc9f368ee47 Mon Sep 17 00:00:00 2001 From: Nikita Kozlov Date: Fri, 19 May 2023 16:16:01 +0200 Subject: [PATCH 07/25] feat: alert on objection phase --- features/aragon/aragonForm.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/features/aragon/aragonForm.tsx b/features/aragon/aragonForm.tsx index a3d91fc..fe20612 100644 --- a/features/aragon/aragonForm.tsx +++ b/features/aragon/aragonForm.tsx @@ -47,6 +47,14 @@ export const AragonForm = () => { ToastError('Voting is closed'); return; } + /* + * Search for VotePhase on + * https://etherscan.io/address/0x72fb5253ad16307b9e773d2a78cac58e309d5ba4#code + */ + if (success && vote?.phase === 1) { + ToastError('Voting is in objection phase'); + return; + } const callData = await encodeCalldata(parseInt(voteId), success); await aragonVote(callData); From 2f8e5ee976f028f6a4725ad8de60d542a8e29599 Mon Sep 17 00:00:00 2001 From: Nikita Kozlov Date: Mon, 22 May 2023 19:37:05 +0200 Subject: [PATCH 08/25] feat: clear amount on form submit --- features/claim/claimForm.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/features/claim/claimForm.tsx b/features/claim/claimForm.tsx index 0a10f8c..56b4536 100644 --- a/features/claim/claimForm.tsx +++ b/features/claim/claimForm.tsx @@ -65,8 +65,9 @@ export const ClaimForm: FC = () => { const { amount, address } = data; await claim(amount, address); resetCache(); + setValue('amount', ''); }, - [claim, resetCache], + [claim, resetCache, setValue], ); const handleUseCustomAddress = useCallback(() => { From 07319e131c4c07312e1432c7415af35a1e703836 Mon Sep 17 00:00:00 2001 From: Nikita Kozlov Date: Mon, 22 May 2023 19:45:42 +0200 Subject: [PATCH 09/25] fix: better error handling --- features/claim/claimForm.tsx | 13 ++++++++++--- shared/ui/transaction/transaction.tsx | 1 + 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/features/claim/claimForm.tsx b/features/claim/claimForm.tsx index 56b4536..e1a5189 100644 --- a/features/claim/claimForm.tsx +++ b/features/claim/claimForm.tsx @@ -63,9 +63,16 @@ export const ClaimForm: FC = () => { const handleClaim = useCallback( async (data: ClaimFormData) => { const { amount, address } = data; - await claim(amount, address); - resetCache(); - setValue('amount', ''); + try { + await claim(amount, address); + resetCache(); + setValue('amount', ''); + } catch (e) { + /* + * Intentionally doing nothing, we just want to abort the flow if there is an error. + * Not catching an error will trigger error monitoring without a good reason (e.g. rejected by user). + */ + } }, [claim, resetCache, setValue], ); diff --git a/shared/ui/transaction/transaction.tsx b/shared/ui/transaction/transaction.tsx index 475e2e1..2a10e7e 100644 --- a/shared/ui/transaction/transaction.tsx +++ b/shared/ui/transaction/transaction.tsx @@ -124,6 +124,7 @@ export const transaction = async ( } catch (error) { if (pendingToastId) toast.dismiss(pendingToastId); showError(error); + throw error; } return result; From 39f76b5930536c27369c8022b462bd2a66d88a46 Mon Sep 17 00:00:00 2001 From: Nikita Kozlov Date: Tue, 23 May 2023 13:23:26 +0200 Subject: [PATCH 10/25] feat: added title and fixed host --- pages/_document.tsx | 18 ++++++++++++------ public/manifest.json | 6 +++--- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/pages/_document.tsx b/pages/_document.tsx index b2ba544..a07ddf4 100644 --- a/pages/_document.tsx +++ b/pages/_document.tsx @@ -11,18 +11,17 @@ import { ServerStyleSheet } from 'styled-components'; import { dynamics } from '../config'; // for prod and dev use https and real domain -let host = 'http://localhost'; +let host = 'http://localhost:3000'; const metaTitle = 'TRP UI | Lido'; const metaDescription = 'Lido Token Rewards Plan for the contributors.'; -const metaPreviewImgUrl = `${host}/lido-preview.png`; const CustomDocument = () => { + const metaPreviewImgUrl = `${host}/lido-preview.png`; + return ( - - { sizes="16x16" href="/favicon-16x16.png" /> + + + + {metaTitle} + + + + - - +