diff --git a/.env.sample b/.env.sample index c1a2adff..df9fcb1a 100644 --- a/.env.sample +++ b/.env.sample @@ -1,6 +1,5 @@ # EL_RPC_URLS_{CHAIN_ID} list or URLs delimeted by commas, first entry is primary, else are fallbacks EL_RPC_URLS_1= -EL_RPC_URLS_5= EL_RPC_URLS_17000= # etherscan api key diff --git a/.github/workflows/ci-dev-goerli.yml b/.github/workflows/ci-dev-goerli.yml deleted file mode 100644 index 2f4e4906..00000000 --- a/.github/workflows/ci-dev-goerli.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: CI Dev Goerli - -on: - workflow_dispatch: - push: - branches: - - goerli - paths-ignore: - - ".github/**" - -permissions: {} - -jobs: - # test: - # ... - - deploy: - runs-on: ubuntu-latest - # needs: test - name: Build and deploy - steps: - - name: Testnet deploy - uses: lidofinance/dispatch-workflow@v1 - env: - APP_ID: ${{ secrets.APP_ID }} - APP_PRIVATE_KEY: ${{ secrets.APP_PRIVATE_KEY }} - TARGET_REPO: "lidofinance/infra-mainnet" - TARGET_WORKFLOW: "deploy_testnet_dao_voting_ui.yaml" - TARGET: "goerli" diff --git a/modules/blockChain/chains.ts b/modules/blockChain/chains.ts index 3305889d..a2f69ee3 100644 --- a/modules/blockChain/chains.ts +++ b/modules/blockChain/chains.ts @@ -3,15 +3,6 @@ import { CHAINS } from '@lido-sdk/constants' export const ChainNames = { [CHAINS.Mainnet]: 'Mainnet', - [CHAINS.Ropsten]: 'Ropsten', - [CHAINS.Rinkeby]: 'Rinkeby', - [CHAINS.Goerli]: 'Goerli', - [CHAINS.Kovan]: 'Kovan', - [CHAINS.Kintsugi]: 'Kintsugi', - [CHAINS.Kiln]: 'Kiln', - [CHAINS.Moonbeam]: 'Moonbeam', - [CHAINS.Moonriver]: 'Moonriver', - [CHAINS.Moonbase]: 'Moonbase', [CHAINS.Holesky]: 'Holesky', } as const diff --git a/modules/blockChain/contractAddresses.ts b/modules/blockChain/contractAddresses.ts index f8f13e67..a8e8e2d9 100644 --- a/modules/blockChain/contractAddresses.ts +++ b/modules/blockChain/contractAddresses.ts @@ -3,61 +3,51 @@ import { ChainAddressMap } from './types' export const AragonVoting: ChainAddressMap = { [CHAINS.Mainnet]: '0x2e59A20f205bB85a89C53f1936454680651E618e', - [CHAINS.Goerli]: '0xbc0B67b4553f4CF52a913DE9A6eD0057E2E758Db', [CHAINS.Holesky]: '0xdA7d2573Df555002503F29aA4003e398d28cc00f', } export const GovernanceToken: ChainAddressMap = { [CHAINS.Mainnet]: '0x5A98FcBEA516Cf06857215779Fd812CA3beF1B32', - [CHAINS.Goerli]: '0x56340274fB5a72af1A3C6609061c451De7961Bd4', [CHAINS.Holesky]: '0x14ae7daeecdf57034f3E9db8564e46Dba8D97344', } export const TokenManager: ChainAddressMap = { [CHAINS.Mainnet]: '0xf73a1260d222f447210581ddf212d915c09a3249', - [CHAINS.Goerli]: '0xDfe76d11b365f5e0023343A367f0b311701B3bc1', [CHAINS.Holesky]: '0xFaa1692c6eea8eeF534e7819749aD93a1420379A', } export const AragonFinance: ChainAddressMap = { [CHAINS.Mainnet]: '0xb9e5cbb9ca5b0d659238807e84d0176930753d86', - [CHAINS.Goerli]: '0x75c7b1D23f1cad7Fb4D60281d7069E46440BC179', [CHAINS.Holesky]: '0xf0F281E5d7FBc54EAFcE0dA225CDbde04173AB16', } export const NodeOperatorsRegistry: ChainAddressMap = { [CHAINS.Mainnet]: '0x55032650b14df07b85bF18A3a3eC8E0Af2e028d5', - [CHAINS.Goerli]: '0x9D4AF1Ee19Dad8857db3a45B0374c81c8A1C6320', [CHAINS.Holesky]: '0x595F64Ddc3856a3b5Ff4f4CC1d1fb4B46cFd2bAC', } export const AragonAgent: ChainAddressMap = { [CHAINS.Mainnet]: '0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c', - [CHAINS.Goerli]: '0x4333218072D5d7008546737786663c38B4D561A4', [CHAINS.Holesky]: '0xE92329EC7ddB11D25e25b3c21eeBf11f15eB325d', } export const AragonACL: ChainAddressMap = { [CHAINS.Mainnet]: '0x9895F0F17cc1d1891b6f18ee0b483B6f221b37Bb', - [CHAINS.Goerli]: '0xb3cf58412a00282934d3c3e73f49347567516e98', [CHAINS.Holesky]: '0xfd1E42595CeC3E83239bf8dFc535250e7F48E0bC', } export const VotingRepo: ChainAddressMap = { [CHAINS.Mainnet]: '0x4Ee3118E3858E8D7164A634825BfE0F73d99C792', - [CHAINS.Goerli]: '0x14de4f901ce0b81f4efca594ad7b70935c276806', [CHAINS.Holesky]: '0x2997EA0D07D79038D83Cb04b3BB9A2Bc512E3fDA', } export const LidoDAO: ChainAddressMap = { [CHAINS.Mainnet]: '0xb8FFC3Cd6e7Cf5a098A1c92F48009765B24088Dc', - [CHAINS.Goerli]: '0x1dD91b354Ebd706aB3Ac7c727455C7BAA164945A', [CHAINS.Holesky]: '0x3b03f75Ec541Ca11a223bB58621A3146246E1644', } export const EasyTrack: ChainAddressMap = { [CHAINS.Mainnet]: '0xF0211b7660680B49De1A7E9f25C65660F0a13Fea', - [CHAINS.Goerli]: '0xAf072C8D368E4DD4A9d4fF6A76693887d6ae92Af', [CHAINS.Holesky]: '0x1763b9ED3586B08AE796c7787811a2E1bc16163a', } @@ -67,131 +57,109 @@ export const TokenRecovererForManagerContracts: ChainAddressMap = { export const LidoAppRepo: ChainAddressMap = { [CHAINS.Mainnet]: '0xF5Dc67E54FC96F993CD06073f71ca732C1E654B1', - [CHAINS.Goerli]: '0xE9eDe497d2417fd980D8B5338232666641B9B9aC', [CHAINS.Holesky]: '0xA37fb4C41e7D30af5172618a863BBB0f9042c604', } export const NodeOperatorsRegistryRepo: ChainAddressMap = { [CHAINS.Mainnet]: '0x0D97E876ad14DB2b183CFeEB8aa1A5C788eB1831', - [CHAINS.Goerli]: '0x5F867429616b380f1Ca7a7283Ff18C53a0033073', [CHAINS.Holesky]: '0x4E8970d148CB38460bE9b6ddaab20aE2A74879AF', } export const Steth: ChainAddressMap = { [CHAINS.Mainnet]: '0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84', - [CHAINS.Goerli]: '0x1643e812ae58766192cf7d2cf9567df2c37e9b7f', [CHAINS.Holesky]: '0x3F1c547b21f65e10480dE3ad8E19fAAC46C95034', } export const OracleRepo: ChainAddressMap = { [CHAINS.Mainnet]: '0xF9339DE629973c60c4d2b76749c81E6F40960E3A', - [CHAINS.Goerli]: '0x9234e37Adeb44022A078557D9943b72AB89bF36a', [CHAINS.Holesky]: '0xB3d74c319C0C792522705fFD3097f873eEc71764', } export const LegacyOracle: ChainAddressMap = { [CHAINS.Mainnet]: '0x442af784A788A5bd6F42A01Ebe9F287a871243fb', - [CHAINS.Goerli]: '0x24d8451BC07e7aF4Ba94F69aCDD9ad3c6579D9FB', [CHAINS.Holesky]: '0x072f72BE3AcFE2c52715829F2CD9061A6C8fF019', } export const CompositePostRebaseBeaconReceiver: ChainAddressMap = { [CHAINS.Mainnet]: '0x55a7E1cbD678d9EbD50c7d69Dc75203B0dBdD431', - [CHAINS.Goerli]: '0x5d2113f7691ac6Df5E3f41Fb938429ACEAD2C94f', } export const DepositSecurityModule: ChainAddressMap = { [CHAINS.Mainnet]: '0x710b3303fb508a84f10793c1106e32be873c24cd', - [CHAINS.Goerli]: '0x7dc1c1ff64078f73c98338e2f17d1996ffbb2ede', [CHAINS.Holesky]: '0x045dd46212A178428c088573A7d102B9d89a022A', } export const WithdrawalVault: ChainAddressMap = { [CHAINS.Mainnet]: '0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f', - [CHAINS.Goerli]: '0xdc62f9e8C34be08501Cdef4EBDE0a280f576D762', [CHAINS.Holesky]: '0xF0179dEC45a37423EAD4FaD5fCb136197872EAd9', } export const ShapellaUpgradeTemplate: ChainAddressMap = { [CHAINS.Mainnet]: '0xa818ff9ec93122bf9401ab4340c42de638cd600a', - [CHAINS.Goerli]: '0xD2fEf3d3544ddf64028784aC3f166413A2A61393', } export const StakingRouter: ChainAddressMap = { [CHAINS.Mainnet]: '0xFdDf38947aFB03C621C71b06C9C70bce73f12999', - [CHAINS.Goerli]: '0xa3dbd317e53d363176359e10948ba0b1c0a4c820', [CHAINS.Holesky]: '0xd6EbF043D30A7fe46D1Db32BA90a0A51207FE229', } export const LidoLocator: ChainAddressMap = { [CHAINS.Mainnet]: '0xC1d0b3DE6792Bf6b4b37EccdcC24e45978Cfd2Eb', - [CHAINS.Goerli]: '0x1eDf09b5023DC86737b59dE68a8130De878984f5', [CHAINS.Holesky]: '0x28FAB2059C713A7F9D8c86Db49f9bb0e96Af1ef8', } export const WithdrawalQueueERC721: ChainAddressMap = { [CHAINS.Mainnet]: '0x889edC2eDab5f40e902b864aD4d7AdE8E412F9B1', - [CHAINS.Goerli]: '0xcf117961421ca9e546cd7f50bc73abcdb3039533', [CHAINS.Holesky]: '0xc7cc160b58F8Bb0baC94b80847E2CF2800565C50', } export const OracleReportSanityChecker: ChainAddressMap = { [CHAINS.Mainnet]: '0x9305c1Dbfe22c12c66339184C0025d7006f0f1cC', - [CHAINS.Goerli]: '0xbf74600040F91D3560d5757280727FB00c64Fd2E', [CHAINS.Holesky]: '0xF0d576c7d934bBeCc68FE15F1c5DAF98ea2B78bb', } export const OracleDaemonConfig: ChainAddressMap = { [CHAINS.Mainnet]: '0xbf05A929c3D7885a6aeAd833a992dA6E5ac23b09', - [CHAINS.Goerli]: '0xE9CC5bD91543cdc9788454EE5063E2CD76B5206d', [CHAINS.Holesky]: '0xC01fC1F2787687Bc656EAc0356ba9Db6e6b7afb7', } export const HashConsensusAccountingOracle: ChainAddressMap = { [CHAINS.Mainnet]: '0xD624B08C83bAECF0807Dd2c6880C3154a5F0B288', - [CHAINS.Goerli]: '0x8d87A8BCF8d4e542fd396D1c50223301c164417b', [CHAINS.Holesky]: '0xa067FC95c22D51c3bC35fd4BE37414Ee8cc890d2', } export const HashConsensusValidatorsExitBus: ChainAddressMap = { [CHAINS.Mainnet]: '0x7FaDB6358950c5fAA66Cb5EB8eE5147De3df355a', - [CHAINS.Goerli]: '0x8374B4aC337D7e367Ea1eF54bB29880C3f036A51', [CHAINS.Holesky]: '0xe77Cf1A027d7C10Ee6bb7Ede5E922a181FF40E8f', } export const TRPVestingEscrowFactory: ChainAddressMap = { [CHAINS.Mainnet]: '0xDA1DF6442aFD2EC36aBEa91029794B9b2156ADD0', - [CHAINS.Goerli]: '0x8D20FD1Ac547e035BF01089cFb92459054F82Ff7', [CHAINS.Holesky]: '0x586f0b51d46ac8ac6058702d99cd066ae514e96b', } export const AccountingOracle: ChainAddressMap = { [CHAINS.Mainnet]: '0x852deD011285fe67063a08005c71a85690503Cee', - [CHAINS.Goerli]: '0x76f358A842defa0E179a8970767CFf668Fc134d6', [CHAINS.Holesky]: '0x4E97A3972ce8511D87F334dA17a2C332542a5246', } export const ValidatorsExitBusOracle: ChainAddressMap = { [CHAINS.Mainnet]: '0x0De4Ea0184c2ad0BacA7183356Aea5B8d5Bf5c6e', - [CHAINS.Goerli]: '0xb75A55EFab5A8f5224Ae93B34B25741EDd3da98b', [CHAINS.Holesky]: '0xffDDF7025410412deaa05E3E1cE68FE53208afcb', } export const MEVBoostRelayAllowedList: ChainAddressMap = { [CHAINS.Mainnet]: '0xF95f069F9AD107938F6ba802a3da87892298610E', - [CHAINS.Goerli]: '0xeabE95AC5f3D64aE16AcBB668Ed0efcd81B721Bc', [CHAINS.Holesky]: '0x2d86C5855581194a386941806E38cA119E50aEA3', } export const ExecutionLayerRewardsVault: ChainAddressMap = { [CHAINS.Mainnet]: '0x388C818CA8B9251b393131C08a736A67ccB19297', - [CHAINS.Goerli]: '0x94750381bE1AbA0504C666ee1DB118F68f0780D4', [CHAINS.Holesky]: '0xE73a3602b99f1f913e72F8bdcBC235e206794Ac8', } export const Burner: ChainAddressMap = { [CHAINS.Mainnet]: '0xD15a672319Cf0352560eE76d9e89eAB0889046D3', - [CHAINS.Goerli]: '0x20c61C07C2E2FAb04BF5b4E12ce45a459a18f3B1', [CHAINS.Holesky]: '0x4E46BD7147ccf666E1d73A3A456fC7a68de82eCA', } diff --git a/modules/blockChain/utils/getGnosisSafeLink.ts b/modules/blockChain/utils/getGnosisSafeLink.ts index ac381bd4..f2c52db7 100644 --- a/modules/blockChain/utils/getGnosisSafeLink.ts +++ b/modules/blockChain/utils/getGnosisSafeLink.ts @@ -1,20 +1,9 @@ -import get from 'lodash/get' import { CHAINS } from '@lido-sdk/constants' -const PREFIXES = { - [CHAINS.Mainnet]: 'eth', - [CHAINS.Goerli]: 'gor', - [CHAINS.Holesky]: 'holesky', -} as const - export const getGnosisSafeLink = (chainId: CHAINS, address: string) => { if (chainId === CHAINS.Holesky) { return `https://holesky-safe.protofire.io/transactions/queue?safe=holesky:${address}` } - return `https://app.safe.global/transactions/queue?safe=${get( - PREFIXES, - chainId, - '?', - )}:${address}` + return `https://app.safe.global/transactions/queue?safe=eth:${address}` } diff --git a/modules/delegation/hooks/useDelegateFromPublicListUpdate.ts b/modules/delegation/hooks/useDelegateFromPublicListUpdate.ts new file mode 100644 index 00000000..c0ed6483 --- /dev/null +++ b/modules/delegation/hooks/useDelegateFromPublicListUpdate.ts @@ -0,0 +1,30 @@ +import { UseFormReturn } from 'react-hook-form' +import { DelegationFormInput } from '../types' +import { useDelegateFromPublicList } from '../providers/DelegateFromPublicListContext' +import { useEffect } from 'react' +import { isValidAddress } from 'modules/shared/utils/addressValidation' + +export const useDelegateFromPublicListUpdate = ( + formObject: UseFormReturn, +) => { + const { selectedPublicDelegate, onPublicDelegateReset } = + useDelegateFromPublicList() + + const { getValues, setValue, setFocus } = formObject + + useEffect(() => { + const currentValue = getValues('delegateAddress') + if ( + selectedPublicDelegate && + isValidAddress(selectedPublicDelegate) && + currentValue?.toLowerCase() !== selectedPublicDelegate.toLowerCase() + ) { + setValue('delegateAddress', selectedPublicDelegate, { + shouldValidate: true, + }) + setFocus('delegateAddress') + onPublicDelegateReset() + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [selectedPublicDelegate]) +} diff --git a/modules/delegation/hooks/useDelegateFromQueryUpdate.ts b/modules/delegation/hooks/useDelegateFromQueryUpdate.ts new file mode 100644 index 00000000..29ca98e2 --- /dev/null +++ b/modules/delegation/hooks/useDelegateFromQueryUpdate.ts @@ -0,0 +1,29 @@ +import { UseFormReturn } from 'react-hook-form' +import { DelegationFormInput } from '../types' +import { useRouter } from 'next/router' +import { useEffect } from 'react' +import { isValidAddress } from 'modules/shared/utils/addressValidation' + +export const useDelegateFromQueryUpdate = ( + formObject: UseFormReturn, +) => { + const { query } = useRouter() + + const { getValues, setValue, setFocus } = formObject + + useEffect(() => { + const currentValue = getValues('delegateAddress') + const queryAddress = query.delegateAddress as string | undefined + if ( + queryAddress && + isValidAddress(queryAddress) && + currentValue?.toLowerCase() !== queryAddress.toLowerCase() + ) { + setValue('delegateAddress', queryAddress, { + shouldValidate: true, + }) + setFocus('delegateAddress') + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [query.delegateAddress]) +} diff --git a/modules/delegation/hooks/useDelegateVoteInfo.ts b/modules/delegation/hooks/useDelegateVoteInfo.ts index 66060d5d..4e6a17e9 100644 --- a/modules/delegation/hooks/useDelegateVoteInfo.ts +++ b/modules/delegation/hooks/useDelegateVoteInfo.ts @@ -1,12 +1,10 @@ import { useMemo } from 'react' -import type { - AttemptCastVoteAsDelegateEventObject, - CastVoteEventObject, -} from 'generated/AragonVotingAbi' +import type { AttemptCastVoteAsDelegateEventObject } from 'generated/AragonVotingAbi' +import { CastVoteEvent } from 'modules/votes/types' interface Props { walletAddress: string | null | undefined - eventsVoted: CastVoteEventObject[] | undefined + eventsVoted: CastVoteEvent[] | undefined eventsDelegatesVoted: AttemptCastVoteAsDelegateEventObject[] | undefined } diff --git a/modules/delegation/hooks/useDelegationInfo.ts b/modules/delegation/hooks/useDelegationInfo.ts index 2ce1dbd2..3519f85a 100644 --- a/modules/delegation/hooks/useDelegationInfo.ts +++ b/modules/delegation/hooks/useDelegationInfo.ts @@ -4,6 +4,8 @@ import { constants } from 'ethers' import { ContractSnapshot, ContractVoting } from 'modules/blockChain/contracts' import { useWeb3 } from 'modules/blockChain/hooks/useWeb3' import { SNAPSHOT_LIDO_SPACE_NAME } from '../constants' +import { getPublicDelegateByAddress } from '../utils/getPublicDelegateName' +import { DelegationInfo, PublicDelegate } from '../types' export const useDelegationInfo = () => { const { walletAddress, chainId } = useWeb3() @@ -12,25 +14,39 @@ export const useDelegationInfo = () => { return useLidoSWRImmutable( walletAddress ? ['swr:useDelegationInfo', chainId, walletAddress] : null, - async (_key: string, _chainId: CHAINS, _walletAddress: string) => { + async ( + _key: string, + _chainId: CHAINS, + _walletAddress: string, + ): Promise => { let aragonDelegateAddress: string | null = ( await voting.getDelegate(_walletAddress) ).toLowerCase() + let aragonPublicDelegate: PublicDelegate | null = null if (aragonDelegateAddress === constants.AddressZero) { aragonDelegateAddress = null + } else { + aragonPublicDelegate = getPublicDelegateByAddress(aragonDelegateAddress) } let snapshotDelegateAddress: string | null = null snapshotDelegateAddress = ( await snapshot.delegation(_walletAddress, SNAPSHOT_LIDO_SPACE_NAME) ).toLowerCase() + let snapshotPublicDelegate: PublicDelegate | null = null if (snapshotDelegateAddress === constants.AddressZero) { snapshotDelegateAddress = null + } else { + snapshotPublicDelegate = getPublicDelegateByAddress( + snapshotDelegateAddress, + ) } return { aragonDelegateAddress, + aragonPublicDelegate, snapshotDelegateAddress, + snapshotPublicDelegate, } }, ) diff --git a/modules/delegation/hooks/useIsDelegate.ts b/modules/delegation/hooks/useIsDelegate.ts new file mode 100644 index 00000000..91b46e22 --- /dev/null +++ b/modules/delegation/hooks/useIsDelegate.ts @@ -0,0 +1,20 @@ +import { CHAINS } from '@lido-sdk/constants' +import { useLidoSWR } from '@lido-sdk/react' +import { ContractVoting } from 'modules/blockChain/contracts' +import { useWeb3 } from 'modules/blockChain/hooks/useWeb3' + +export const useIsDelegate = () => { + const { chainId, walletAddress } = useWeb3() + const votingContract = ContractVoting.useRpc() + + return useLidoSWR( + walletAddress ? [`swr:useIsDelegate`, chainId, walletAddress] : null, + async (_key: string, _chainId: CHAINS, _walletAddress: string) => { + const delegatorsCount = await votingContract.getDelegatedVotersCount( + _walletAddress, + ) + + return delegatorsCount.gt(0) + }, + ) +} diff --git a/modules/delegation/providers/DelegationFormContext.tsx b/modules/delegation/providers/DelegationFormContext.tsx index e43f1cec..aa11d4db 100644 --- a/modules/delegation/providers/DelegationFormContext.tsx +++ b/modules/delegation/providers/DelegationFormContext.tsx @@ -5,7 +5,6 @@ import { useContext, useCallback, useState, - useEffect, } from 'react' import { useGovernanceBalance } from 'modules/tokens/hooks/useGovernanceBalance' import { useDelegationInfo } from '../hooks/useDelegationInfo' @@ -20,8 +19,9 @@ import { import { useDelegationFormSubmit } from '../hooks/useDelegationFormSubmit' import { useDelegationRevoke } from '../hooks/useDelegationRevoke' import { ToastSuccess } from '@lidofinance/lido-ui' -import { isValidAddress } from 'modules/shared/utils/addressValidation' -import { useDelegateFromPublicList } from './DelegateFromPublicListContext' +import { useProcessedPublicDelegatesList } from '../ui/PublicDelegateList/useProcessedPublicDelegatesList' +import { useDelegateFromPublicListUpdate } from '../hooks/useDelegateFromPublicListUpdate' +import { useDelegateFromQueryUpdate } from '../hooks/useDelegateFromQueryUpdate' // // Data context @@ -65,7 +65,9 @@ const useDelegationFormNetworkData = (): DelegationFormNetworkData => { return { aragonDelegateAddress: delegationInfo?.aragonDelegateAddress, + aragonPublicDelegate: delegationInfo?.aragonPublicDelegate, snapshotDelegateAddress: delegationInfo?.snapshotDelegateAddress, + snapshotPublicDelegate: delegationInfo?.snapshotPublicDelegate, governanceBalanceStr: governanceBalance?.balanceStr, loading, revalidate, @@ -81,15 +83,18 @@ const useDelegationFormActions = ( const setSubmitting = useCallback(() => setIsSubmitting(true), []) const resetSubmitting = useCallback(() => setIsSubmitting(false), []) + const { update } = useProcessedPublicDelegatesList() + const handleFinish = useCallback( async (hasError?: boolean) => { await networkData.revalidate() + await update() resetSubmitting() if (!hasError) { ToastSuccess('Transaction submitted successfully') } }, - [networkData, resetSubmitting], + [networkData, resetSubmitting, update], ) const { txAragonDelegate, txSnapshotDelegate, submitDelegation } = @@ -129,29 +134,15 @@ export const DelegationFormProvider: FC = ({ mode, }) => { const networkData = useDelegationFormNetworkData() - const { selectedPublicDelegate, onPublicDelegateReset } = - useDelegateFromPublicList() const formObject = useForm({ defaultValues: { delegateAddress: '' }, mode: 'onChange', }) - useEffect(() => { - const currentValue = formObject.getValues('delegateAddress') - if ( - selectedPublicDelegate && - isValidAddress(selectedPublicDelegate) && - currentValue?.toLowerCase() !== selectedPublicDelegate.toLowerCase() - ) { - formObject.setValue('delegateAddress', selectedPublicDelegate, { - shouldValidate: true, - }) - formObject.setFocus('delegateAddress') - onPublicDelegateReset() - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [selectedPublicDelegate]) + useDelegateFromPublicListUpdate(formObject) + + useDelegateFromQueryUpdate(formObject) const { isSubmitting, diff --git a/modules/delegation/publicDelegates.ts b/modules/delegation/publicDelegates.ts index 64e9e480..c11a6799 100644 --- a/modules/delegation/publicDelegates.ts +++ b/modules/delegation/publicDelegates.ts @@ -4,13 +4,6 @@ const basePath = getConfig().publicRuntimeConfig.basePath || '' // The list of public delegates was provided by DAO Ops workstream member export const PUBLIC_DELEGATES = [ - { - name: 'Matt Stam', - avatar: `${basePath}/delegates/matt.jpeg`, - address: '0x33379367200Ac200182ccD4abD71683F2D24e373', - lido: 'https://research.lido.fi/t/matt-stam-delegate-thread/8145', - twitter: 'https://x.com/mattstam_eth', - }, { name: 'Ignas', avatar: `${basePath}/delegates/ignas.png`, @@ -53,13 +46,6 @@ export const PUBLIC_DELEGATES = [ lido: 'https://research.lido.fi/t/anthony-leuts-delegate-thread/8019', twitter: 'https://x.com/A_Leutenegger', }, - { - name: 'Sov', - avatar: `${basePath}/delegates/sov.jpeg`, - address: 'sovereignsignal.eth', - lido: 'https://research.lido.fi/t/sov-delegate-thread/8069', - twitter: 'https://x.com/sovereignsignal', - }, { name: 'Polar', avatar: `${basePath}/delegates/polar.jpeg`, @@ -81,13 +67,6 @@ export const PUBLIC_DELEGATES = [ lido: 'https://research.lido.fi/t/blockworks-research-delegate-thread/8024', twitter: 'https://x.com/blockworksres', }, - { - name: 'Blockful (Daniela Zschaber)', - avatar: `${basePath}/delegates/blockful.jpeg`, - address: '0x3b9F47629cD4D5903cF3eB897aaC4F6b41Dd2589', - lido: 'https://research.lido.fi/t/daniela-zschaber-representing-blockful-delegate-thread/8018', - twitter: 'https://x.com/danimimm', - }, { name: 'karpatkey', avatar: `${basePath}/delegates/karpatkey.png`, @@ -98,14 +77,14 @@ export const PUBLIC_DELEGATES = [ { name: 'StableLab', avatar: `${basePath}/delegates/stablelab.png`, - address: 'stablelab.eth', + address: '0xECC2a9240268BC7a26386ecB49E1Befca2706AC9', lido: 'https://research.lido.fi/t/stablelab-delegate-thread-updated/4904/17', twitter: 'https://x.com/Stablelab', }, { name: 'DegentradingLSD', - avatar: null, - address: 'degentradinglsd3.eth', + avatar: `${basePath}/delegates/degentrading.png`, + address: '0xbeb3364D14DbB4D9A406966B97B9FB3fF8aB7646', lido: 'https://research.lido.fi/t/degentradinglsd-delegate-thread/8149', twitter: 'https://x.com/degentradingLSD', }, @@ -123,38 +102,17 @@ export const PUBLIC_DELEGATES = [ lido: 'https://research.lido.fi/t/eboadom-delegate-thread/8079', twitter: 'https://x.com/eboadom', }, - { - name: 'ReservoirDAO', - avatar: `${basePath}/delegates/reservoir.jpeg`, - address: '0x4f894Bfc9481110278C356adE1473eBe2127Fd3C', - lido: 'https://research.lido.fi/t/reservoirdao-alphagrowth-delegate-platform/8169', - twitter: 'https://x.com/alphagrowth1', - }, - { - name: 'FranklinDAO', - avatar: `${basePath}/delegates/franklin.jpeg`, - address: '0x070341aA5Ed571f0FB2c4a5641409B1A46b4961b', - lido: 'https://research.lido.fi/t/franklin-dao-delegate-platform/8167', - twitter: 'https://x.com/franklin_dao', - }, - { - name: 'Mog', - avatar: `${basePath}/delegates/mog.jpeg`, - address: '0x61Cc8f06F3B6e1a3Fba28CbEbc4c89304b1187D1', - lido: 'https://research.lido.fi/t/mog-delegate-thread/8164', - twitter: 'https://x.com/tungtdinh', - }, { name: 'Pol Lanski', avatar: `${basePath}/delegates/lanski.png`, - address: 'lanski.eth', + address: '0xB6647e02AE6Dd74137cB80b1C24333852E4AF890', lido: 'https://research.lido.fi/t/pol-lanski-delegate-thread/8155', twitter: 'https://x.com/Pol_Lanski', }, { name: 'notjamiedimon', avatar: `${basePath}/delegates/notjamiedimon.jpg`, - address: 'notjamiedimon.eth', + address: '0xCE3b1e215f379A5edDbc1ee80a6dE089c0b92e55', lido: 'https://research.lido.fi/t/notjamiedimon-delegate-thread/8174', twitter: 'https://x.com/regentrading', }, @@ -165,41 +123,13 @@ export const PUBLIC_DELEGATES = [ lido: 'https://research.lido.fi/t/irina-delegate-thread/8217', twitter: 'https://x.com/eth_everstake', }, - { - name: 'marcbcs', - avatar: `${basePath}/delegates/marcbcs.jpeg`, - address: '0x98308b6dA79B47D15e9438CB66831563649Dbd94', - lido: 'https://research.lido.fi/t/marcbcs-delegate-thread/8209', - twitter: 'https://x.com/marcbcs', - }, - { - name: 'Daedalus Collective', - avatar: `${basePath}/delegates/daedalus.png`, - address: '0x04827a54F2e345467beAFEfB9EF76Cb2f2c62D83', - lido: 'https://research.lido.fi/t/daedalus-delegate-thread/8195', - twitter: 'https://x.com/daedalus_angels', - }, { name: 'ProRelGuild', avatar: null, - address: 'prorelguild.eth', + address: '0x7A5959855B6508aF1055Af460331fB697dd08e22', lido: 'https://research.lido.fi/t/prorelguild-delegate-thread/8186', twitter: 'https://x.com/apegenija', }, - { - name: 'Today in DeFi', - avatar: null, - address: '0xf163D77B8EfC151757fEcBa3D463f3BAc7a4D808', - lido: 'https://research.lido.fi/t/today-in-defi-delegate-thread/8207', - twitter: 'https://x.com/todayindefi', - }, - { - name: 'Next Finance Tech', - avatar: `${basePath}/delegates/nxt.jpeg`, - address: '0x18c674F655594F15c490aeEac737895b7903E37f', - lido: 'https://research.lido.fi/t/next-finance-tech-delegate-thread/8204', - twitter: 'https://x.com/nxt_fintech', - }, { name: 'cp0x', avatar: `${basePath}/delegates/cp0x.jpeg`, @@ -214,13 +144,6 @@ export const PUBLIC_DELEGATES = [ lido: 'https://research.lido.fi/t/simply-staking-delegate-thread/8178', twitter: 'https://x.com/SimplyStaking', }, - { - name: 'Boardroom', - avatar: null, - address: 'boardroomgov.eth', - lido: 'https://research.lido.fi/t/boardroom-delegate-thread/8200', - twitter: 'https://x.com/boardroom_info', - }, { name: 'Proof Group', avatar: null, @@ -228,17 +151,10 @@ export const PUBLIC_DELEGATES = [ lido: 'https://research.lido.fi/t/proof-group-delegate-thread/8190', twitter: 'https://x.com/njess', }, - { - name: 'Lemma Solutions', - avatar: `${basePath}/delegates/lemma.jpeg`, - address: '0x58B1b454Dbe5156ACc8FC2139E7238451b59f432', - lido: 'https://research.lido.fi/t/lemma-delegate-thread/8214', - twitter: 'https://x.com/Lemma_Solutions', - }, { name: 'Nansen', avatar: `${basePath}/delegates/nansen.png`, - address: 'nansengov.eth', + address: '0xa4181C75495f60106AE539B7C55c0D263f2fb737', lido: 'https://research.lido.fi/t/nansen-delegate-thread/8303', twitter: 'https://x.com/nansen_ai', }, @@ -252,7 +168,7 @@ export const PUBLIC_DELEGATES = [ { name: 'PGov.eth', avatar: `${basePath}/delegates/pgov.jpeg`, - address: 'pgov.eth', + address: '0x3FB19771947072629C8EEE7995a2eF23B72d4C8A', lido: 'https://research.lido.fi/t/pgov-delegate-thread/8232', twitter: 'https://x.com/PGovTeam', }, diff --git a/modules/delegation/types.ts b/modules/delegation/types.ts index 9fbf6975..864bd7b0 100644 --- a/modules/delegation/types.ts +++ b/modules/delegation/types.ts @@ -1,5 +1,13 @@ import { TransactionSender } from 'modules/blockChain/hooks/useTransactionSender' import { UseFormRegister, UseFormWatch } from 'react-hook-form' +import { PUBLIC_DELEGATES } from './publicDelegates' + +export type DelegationInfo = { + aragonDelegateAddress: string | null | undefined + aragonPublicDelegate: PublicDelegate | null | undefined + snapshotDelegateAddress: string | null | undefined + snapshotPublicDelegate: PublicDelegate | null | undefined +} export type DelegationFormInput = { delegateAddress: string | null @@ -11,12 +19,10 @@ export type DelegationFormLoading = { } export type DelegationFormNetworkData = { - aragonDelegateAddress?: string | null - snapshotDelegateAddress?: string | null governanceBalanceStr?: string loading: DelegationFormLoading revalidate: () => Promise -} +} & DelegationInfo export type DelegationType = 'aragon' | 'snapshot' @@ -32,3 +38,5 @@ export type DelegationFormDataContextValue = DelegationFormNetworkData & { register: UseFormRegister watch: UseFormWatch } + +export type PublicDelegate = typeof PUBLIC_DELEGATES[number] diff --git a/modules/delegation/ui/DelegationAddressPop/DelegationAddressPop.tsx b/modules/delegation/ui/DelegationAddressPop/DelegationAddressPop.tsx index e13cb46e..5cca02b3 100644 --- a/modules/delegation/ui/DelegationAddressPop/DelegationAddressPop.tsx +++ b/modules/delegation/ui/DelegationAddressPop/DelegationAddressPop.tsx @@ -1,14 +1,23 @@ import { useClickAway } from 'react-use' -import { useCallback, useEffect, useRef } from 'react' +import { useCallback, useEffect, useMemo, useRef } from 'react' import { useSimpleReducer } from 'modules/shared/hooks/useSimpleReducer' import { CopyOpenActions } from 'modules/shared/ui/Common/CopyOpenActions' import { IdenticonBadge, Text } from '@lidofinance/lido-ui' import UnionIcon from 'assets/union.com.svg.react' -import { Wrap, Pop, BadgeWrap, IdenticonBadgeWrap, VotedBy } from './PopStyle' +import { + Wrap, + Pop, + BadgeWrap, + IdenticonBadgeWrap, + VotedBy, + PublicDelegateWrap, +} from './PopStyle' import { calcPopoverPosition } from './calcPopoverPosition' +import { getPublicDelegateByAddress } from 'modules/delegation/utils/getPublicDelegateName' +import { PublicDelegateAvatar } from '../PublicDelegateAvatar' type IdenticonBadgeProps = React.ComponentProps @@ -34,6 +43,11 @@ export function DelegationAddressPop({ children, ...badgeProps }: Props) { setState({ isOpened: true }) }, [setState]) + const publicDelegate = useMemo(() => { + if (!delegateAddress) return null + return getPublicDelegateByAddress(delegateAddress) + }, [delegateAddress]) + useEffect(() => { const wrapEl = wrapRef.current const popEl = popRef.current @@ -69,13 +83,25 @@ export function DelegationAddressPop({ children, ...badgeProps }: Props) { Voted By - + {publicDelegate ? ( + + + {publicDelegate.name} + + + + ) : ( + + )} diff --git a/modules/delegation/ui/DelegationAddressPop/PopStyle.tsx b/modules/delegation/ui/DelegationAddressPop/PopStyle.tsx index 9b08d52f..6b82189c 100644 --- a/modules/delegation/ui/DelegationAddressPop/PopStyle.tsx +++ b/modules/delegation/ui/DelegationAddressPop/PopStyle.tsx @@ -60,6 +60,7 @@ export const VotedBy = styled.div` gap: 8px; margin-bottom: 8px; margin-top: 24px; + padding-left: 16px; ` export const IdenticonBadgeWrap = styled(IdenticonBadge)` @@ -69,3 +70,16 @@ export const IdenticonBadgeWrap = styled(IdenticonBadge)` justify-content: space-between; align-items: center; ` + +export const PublicDelegateWrap = styled.div` + padding: 4px; + display: flex; + align-items: center; + justify-content: space-between; + border-radius: ${({ theme }) => theme.borderRadiusesMap.xl}px; + + & > span { + padding: 0px 6px; + flex: 1; + } +` diff --git a/modules/delegation/ui/DelegationForm/DelegationAddressInput.tsx b/modules/delegation/ui/DelegationForm/DelegationAddressInput.tsx index eb18343d..66d30ed1 100644 --- a/modules/delegation/ui/DelegationForm/DelegationAddressInput.tsx +++ b/modules/delegation/ui/DelegationForm/DelegationAddressInput.tsx @@ -2,7 +2,6 @@ import { useWeb3 } from 'modules/blockChain/hooks/useWeb3' import { useDelegationFormData } from 'modules/delegation/providers/DelegationFormContext' import { validateAddress } from 'modules/delegation/utils/validateAddress' import { InputControl } from 'modules/shared/ui/Controls/Input' -import { hasIncorrectLength } from 'modules/delegation/utils/hasIncorrectLength' export function DelegationAddressInput() { const { isWalletConnected, walletAddress } = useWeb3() @@ -27,9 +26,7 @@ export function DelegationAddressInput() { if (value.length > 42) { return 'Address is too long' } - if (hasIncorrectLength(value)) { - return true - } + const addressErr = validateAddress(value) if (addressErr) { return addressErr diff --git a/modules/delegation/ui/DelegationForm/DelegationForm.tsx b/modules/delegation/ui/DelegationForm/DelegationForm.tsx index c446e282..ab8e2fa6 100644 --- a/modules/delegation/ui/DelegationForm/DelegationForm.tsx +++ b/modules/delegation/ui/DelegationForm/DelegationForm.tsx @@ -10,6 +10,7 @@ import { DelegationFormSubmitButton } from './DelegationFormSubmitButton' import { DelegationFormFootNote } from './DelegationFormFootNote' import { DelegationFormController } from './DelegationFormController' import { DelegationTxStatus } from './DelegationTxStatus' +import { DelegationFormPublicDelegateTooltip } from './DelegationFormPublicDelegateTooltip' type Props = DelegationFormProviderProps & { onCustomizeClick?: () => void @@ -22,6 +23,7 @@ export function DelegationForm({ onCustomizeClick, ...providerProps }: Props) { + diff --git a/modules/delegation/ui/DelegationForm/DelegationFormPublicDelegateTooltip.tsx b/modules/delegation/ui/DelegationForm/DelegationFormPublicDelegateTooltip.tsx new file mode 100644 index 00000000..7db20421 --- /dev/null +++ b/modules/delegation/ui/DelegationForm/DelegationFormPublicDelegateTooltip.tsx @@ -0,0 +1,41 @@ +import { useEffect, useState } from 'react' +import { useDelegationFormData } from 'modules/delegation/providers/DelegationFormContext' +import { PublicDelegate } from 'modules/delegation/types' +import { getPublicDelegateByAddress } from 'modules/delegation/utils/getPublicDelegateName' +import { DelegationFormFootNoteStyled } from './DelegationFormStyle' +import { isValidAddress } from 'modules/shared/utils/addressValidation' + +export function DelegationFormPublicDelegateTooltip() { + const { watch } = useDelegationFormData() + const [selectedPublicDelegate, setSelectedPublicDelegate] = + useState(null) + + useEffect(() => { + const { unsubscribe } = watch(({ delegateAddress }) => { + if (isValidAddress(delegateAddress)) { + const publicDelegate = getPublicDelegateByAddress(delegateAddress) + + if (!publicDelegate && selectedPublicDelegate) { + setSelectedPublicDelegate(null) + } else if ( + publicDelegate && + publicDelegate.address !== selectedPublicDelegate?.address + ) { + setSelectedPublicDelegate(publicDelegate) + } + } else if (selectedPublicDelegate) { + setSelectedPublicDelegate(null) + } + }) + return () => unsubscribe() + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [watch, selectedPublicDelegate]) + + if (!selectedPublicDelegate) return null + + return ( + + Public delegate: {selectedPublicDelegate.name} + + ) +} diff --git a/modules/delegation/ui/DelegationForm/DelegationFormSubmitButton.tsx b/modules/delegation/ui/DelegationForm/DelegationFormSubmitButton.tsx index 8699ac0e..7f0d813c 100644 --- a/modules/delegation/ui/DelegationForm/DelegationFormSubmitButton.tsx +++ b/modules/delegation/ui/DelegationForm/DelegationFormSubmitButton.tsx @@ -3,8 +3,6 @@ import { useWeb3 } from 'modules/blockChain/hooks/useWeb3' import { useDelegationFormData } from 'modules/delegation/providers/DelegationFormContext' import { useConnectWalletModal } from 'modules/wallet/ui/ConnectWalletModal' import { DelegateButton, HiddenButton } from './DelegationFormStyle' -import { useFormState } from 'react-hook-form' -import { hasIncorrectLength } from 'modules/delegation/utils/hasIncorrectLength' import { useConfirmReDelegateModal } from './ConfirmReDelegateModal' import { Text } from '@lidofinance/lido-ui' @@ -29,7 +27,6 @@ export function DelegationFormSubmitButton({ onCustomizeClick }: Props) { }, [ref]) const isSimple = mode === 'simple' - const { errors } = useFormState() const [delegateAddressInput] = watch(['delegateAddress']) const match = useMemo(() => { @@ -133,18 +130,9 @@ export function DelegationFormSubmitButton({ onCustomizeClick }: Props) { ) } - const isDisabled = Boolean( - hasIncorrectLength(delegateAddressInput ?? '') || - errors['delegateAddress']?.message, - ) - if (!match.isRedelegate || !isSimple) { return ( - + {buttonText} ) @@ -156,11 +144,10 @@ export function DelegationFormSubmitButton({ onCustomizeClick }: Props) { type="submit" loading={isSubmitting} onClick={onSubmitDialog} - disabled={isDisabled} > {buttonText} - + ) } diff --git a/modules/delegation/ui/DelegationForm/DelegationStatus/DelegationAddressBadge.tsx b/modules/delegation/ui/DelegationForm/DelegationStatus/DelegationAddressBadge.tsx index 2747f222..15dc60c1 100644 --- a/modules/delegation/ui/DelegationForm/DelegationStatus/DelegationAddressBadge.tsx +++ b/modules/delegation/ui/DelegationForm/DelegationStatus/DelegationAddressBadge.tsx @@ -6,27 +6,42 @@ import { DelegationAddressBadgeStyled, } from './DelegationStatusStyle' import { useDelegationFormData } from 'modules/delegation/providers/DelegationFormContext' -import { DelegationType } from 'modules/delegation/types' +import { DelegationType, PublicDelegate } from 'modules/delegation/types' import { useConfirmRevokeModal } from './ConfirmRevokeModal' +import { PublicDelegateAvatar } from '../../PublicDelegateAvatar' type Props = { address: string + publicDelegate: PublicDelegate | null | undefined type: DelegationType } -export function DelegationAddressBadge({ address, type }: Props) { +export function DelegationAddressBadge({ + address, + publicDelegate, + type, +}: Props) { const { isSubmitting, onRevoke } = useDelegationFormData() const { openModal: openConfirmModal } = useConfirmRevokeModal({ title: `Revoke ${type} delegation?`, onRevoke: onRevoke(type), }) + return ( - - - {trimAddress(address, 4)} + {publicDelegate ? ( + + ) : ( + + )} + + {publicDelegate?.name ?? trimAddress(address, 4)} diff --git a/modules/delegation/ui/DelegationForm/DelegationStatus/DelegationStatus.tsx b/modules/delegation/ui/DelegationForm/DelegationStatus/DelegationStatus.tsx index 49acefa6..bfb5dcac 100644 --- a/modules/delegation/ui/DelegationForm/DelegationStatus/DelegationStatus.tsx +++ b/modules/delegation/ui/DelegationForm/DelegationStatus/DelegationStatus.tsx @@ -14,8 +14,14 @@ import SnapshotSvg from 'assets/snapshot.com.svg.react' export function DelegationStatus() { const { isWalletConnected } = useWeb3() - const { mode, aragonDelegateAddress, snapshotDelegateAddress, loading } = - useDelegationFormData() + const { + mode, + aragonDelegateAddress, + snapshotDelegateAddress, + aragonPublicDelegate, + snapshotPublicDelegate, + loading, + } = useDelegationFormData() if (!isWalletConnected) { return null @@ -31,6 +37,7 @@ export function DelegationStatus() { {aragonDelegateAddress ? ( @@ -48,6 +55,7 @@ export function DelegationStatus() { {snapshotDelegateAddress ? ( ) : ( @@ -62,12 +70,18 @@ export function DelegationStatus() { const delegateAddress = mode === 'aragon' ? aragonDelegateAddress : snapshotDelegateAddress + const publicDelegate = + mode === 'aragon' ? aragonPublicDelegate : snapshotPublicDelegate return ( Delegated to {delegateAddress ? ( - + ) : ( {loading.isDelegationInfoLoading ? 'Loading...' : 'Not delegated'} diff --git a/modules/delegation/ui/DelegationSettings/DelegationSettings.tsx b/modules/delegation/ui/DelegationSettings/DelegationSettings.tsx index 5b0e7946..09164f30 100644 --- a/modules/delegation/ui/DelegationSettings/DelegationSettings.tsx +++ b/modules/delegation/ui/DelegationSettings/DelegationSettings.tsx @@ -6,8 +6,12 @@ import { DelegationForm } from '../DelegationForm' import { PublicDelegateList } from '../PublicDelegateList' import { DelegateFromPublicListProvider } from '../../providers/DelegateFromPublicListContext' -export function DelegationSettings() { - const [isSimpleModeOn, setIsSimpleModeOn] = useState(true) +type Props = { + customizeMode: boolean +} + +export function DelegationSettings({ customizeMode }: Props) { + const [isSimpleModeOn, setIsSimpleModeOn] = useState(!customizeMode) const isMobile = useBreakpoint('md') return ( diff --git a/modules/delegation/ui/DelegationTabs/DelegationTabs.tsx b/modules/delegation/ui/DelegationTabs/DelegationTabs.tsx index b9693748..fd6a0434 100644 --- a/modules/delegation/ui/DelegationTabs/DelegationTabs.tsx +++ b/modules/delegation/ui/DelegationTabs/DelegationTabs.tsx @@ -3,6 +3,7 @@ import { Switch } from 'modules/delegation/ui/Switch' import { NoSSRWrapper } from 'modules/shared/ui/Utils/NoSSRWrapper' import { DelegationSettings } from '../DelegationSettings' import { DelegatorsList } from '../DelegatorsList' +import { useIsDelegate } from 'modules/delegation/hooks/useIsDelegate' const NAV_ROUTES = [ { name: 'Delegate', path: delegation }, @@ -10,17 +11,29 @@ const NAV_ROUTES = [ ] export type DelegationTabsLayoutProps = { - mode: 'delegation' | 'delegators' + mode: 'delegation' | 'customize' | 'delegators' } export const DelegationTabs = ({ mode }: DelegationTabsLayoutProps) => { const isDelegatorsMode = mode === 'delegators' + const { data: isDelegate, initialLoading } = useIsDelegate() + + if (initialLoading) { + return null + } + return ( <> - - {isDelegatorsMode ? : } + {isDelegate && ( + + )} + {isDelegatorsMode ? ( + + ) : ( + + )} ) diff --git a/modules/delegation/ui/PublicDelegateList/PublicDelegateAvatar.tsx b/modules/delegation/ui/PublicDelegateAvatar/PublicDelegateAvatar.tsx similarity index 67% rename from modules/delegation/ui/PublicDelegateList/PublicDelegateAvatar.tsx rename to modules/delegation/ui/PublicDelegateAvatar/PublicDelegateAvatar.tsx index c864bdd1..2e09daa8 100644 --- a/modules/delegation/ui/PublicDelegateList/PublicDelegateAvatar.tsx +++ b/modules/delegation/ui/PublicDelegateAvatar/PublicDelegateAvatar.tsx @@ -1,23 +1,24 @@ import Image from 'next/image' -import { AvatarWrap } from './PublicDelegateListStyle' +import { AvatarWrap } from './PublicDelegateAvatarStyle' import AvatarSvg from 'assets/avatar.com.svg.react' type Props = { avatarSrc: string | null | undefined + size?: number } -export function PublicDelegateAvatar({ avatarSrc }: Props) { +export function PublicDelegateAvatar({ avatarSrc, size }: Props) { if (!avatarSrc) { return ( - + ) } return ( - + ` + position: relative; + border-radius: 50%; + width: ${({ size }) => `${size ?? 28}px`}; + height: ${({ size }) => `${size ?? 28}px`}; + overflow: hidden; + flex-shrink: 0; + + & > img, + & > svg { + width: 100%; + height: 100%; + } + + & > img[src=''] { + display: none; + } + + @media (max-width: ${BREAKPOINT_MD}) { + width: ${({ size }) => `${size ?? 32}px`}; + height: ${({ size }) => `${size ?? 32}px`}; + } +` diff --git a/modules/delegation/ui/PublicDelegateAvatar/index.ts b/modules/delegation/ui/PublicDelegateAvatar/index.ts new file mode 100644 index 00000000..3802d759 --- /dev/null +++ b/modules/delegation/ui/PublicDelegateAvatar/index.ts @@ -0,0 +1 @@ +export { PublicDelegateAvatar } from './PublicDelegateAvatar' diff --git a/modules/delegation/ui/PublicDelegateList/PublicDelegateListItem.tsx b/modules/delegation/ui/PublicDelegateList/PublicDelegateListItem.tsx index e1a4a925..e728e7a3 100644 --- a/modules/delegation/ui/PublicDelegateList/PublicDelegateListItem.tsx +++ b/modules/delegation/ui/PublicDelegateList/PublicDelegateListItem.tsx @@ -12,7 +12,7 @@ import { AddressPop } from 'modules/shared/ui/Common/AddressPop' import { isValidAddress } from 'modules/shared/utils/addressValidation' import { formatBalance } from 'modules/blockChain/utils/formatBalance' import { ExternalLink } from 'modules/shared/ui/Common/ExternalLink' -import { PublicDelegateAvatar } from './PublicDelegateAvatar' +import { PublicDelegateAvatar } from '../PublicDelegateAvatar' import { useDelegateFromPublicList } from 'modules/delegation/providers/DelegateFromPublicListContext' import XSocialSvg from 'assets/x.social.com.svg.react' diff --git a/modules/delegation/utils/getPublicDelegateName.ts b/modules/delegation/utils/getPublicDelegateName.ts new file mode 100644 index 00000000..308aaa65 --- /dev/null +++ b/modules/delegation/utils/getPublicDelegateName.ts @@ -0,0 +1,9 @@ +import { PUBLIC_DELEGATES } from '../publicDelegates' +import { PublicDelegate } from '../types' + +export const getPublicDelegateByAddress = ( + address: string, +): PublicDelegate | null => + PUBLIC_DELEGATES.find( + delegate => delegate.address.toLowerCase() === address.toLowerCase(), + ) ?? null diff --git a/modules/delegation/utils/hasIncorrectLength.ts b/modules/delegation/utils/hasIncorrectLength.ts deleted file mode 100644 index e0eb5d93..00000000 --- a/modules/delegation/utils/hasIncorrectLength.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const hasIncorrectLength = (address: string) => - ![40, 42].includes(`${address}`.length) diff --git a/modules/shared/utils/addressValidation.ts b/modules/shared/utils/addressValidation.ts index cb092e6a..3c869cb6 100644 --- a/modules/shared/utils/addressValidation.ts +++ b/modules/shared/utils/addressValidation.ts @@ -2,7 +2,9 @@ import { ethers } from 'ethers' const regex = new RegExp('[-a-zA-Z0-9@._]{1,256}.eth') -export const isValidAddress = (address: string) => - ethers.utils.isAddress(address) +export const isValidAddress = ( + address: string | null | undefined, +): address is string => + address?.length ? ethers.utils.isAddress(address) : false export const isValidEns = (ens: string) => regex.test(ens) diff --git a/modules/votes/constants.ts b/modules/votes/constants.ts new file mode 100644 index 00000000..b2349348 --- /dev/null +++ b/modules/votes/constants.ts @@ -0,0 +1,118 @@ +import { solidityKeccak256 } from 'ethers/lib/utils' + +const keccakRole = (role: string) => solidityKeccak256(['string'], [role]) + +export const LIDO_ROLES: Partial> = { + [keccakRole('APP_MANAGER_ROLE')]: 'APP MANAGER ROLE', + [keccakRole('UNSAFELY_MODIFY_VOTE_TIME_ROLE')]: + 'UNSAFELY MODIFY VOTE TIME ROLE', + [keccakRole('MODIFY_QUORUM_ROLE')]: 'MODIFY QUORUM ROLE', + [keccakRole('MODIFY_SUPPORT_ROLE')]: 'MODIFY SUPPORT ROLE', + [keccakRole('CREATE_VOTES_ROLE')]: 'CREATE VOTES ROLE', + [keccakRole('ISSUE_ROLE')]: 'ISSUE ROLE', + [keccakRole('ASSIGN_ROLE')]: 'ASSIGN ROLE', + [keccakRole('BURN_ROLE')]: 'BURN ROLE', + [keccakRole('MINT_ROLE')]: 'MINT ROLE', + [keccakRole('REVOKE_VESTINGS_ROLE')]: 'REVOKE VESTINGS ROLE', + [keccakRole('CREATE_PAYMENTS_ROLE')]: 'CREATE PAYMENTS ROLE', + [keccakRole('CHANGE_PERIOD_ROLE')]: 'CHANGE PERIOD ROLE', + [keccakRole('CHANGE_BUDGETS_ROLE')]: 'CHANGE BUDGETS ROLE', + [keccakRole('EXECUTE_PAYMENTS_ROLE')]: 'EXECUTE PAYMENTS ROLE', + [keccakRole('MANAGE_PAYMENTS_ROLE')]: 'MANAGE PAYMENTS ROLE', + [keccakRole('CREATE_PAYMENTS_ROLE')]: 'CREATE PAYMENTS ROLE', + [keccakRole('ADD_PROTECTED_TOKEN_ROLE')]: 'ADD PROTECTED TOKEN ROLE', + [keccakRole('TRANSFER_ROLE')]: 'TRANSFER ROLE', + [keccakRole('RUN_SCRIPT_ROLE')]: 'RUN SCRIPT ROLE', + [keccakRole('SAFE_EXECUTE_ROLE')]: 'SAFE EXECUTE ROLE', + [keccakRole('REMOVE_PROTECTED_TOKEN_ROLE')]: 'REMOVE PROTECTED TOKEN ROLE', + [keccakRole('DESIGNATE_SIGNER_ROLE')]: 'DESIGNATE SIGNER ROLE', + [keccakRole('EXECUTE_ROLE')]: 'EXECUTE ROLE', + [keccakRole('CREATE_PERMISSIONS_ROLE')]: 'CREATE PERMISSIONS ROLE', + [keccakRole('CREATE_VERSION_ROLE')]: 'CREATE VERSION ROLE', + [keccakRole('SET_NODE_OPERATOR_ADDRESS_ROLE')]: + 'SET NODE OPERATOR ADDRESS ROLE', + [keccakRole('SET_NODE_OPERATOR_NAME_ROLE')]: 'SET NODE OPERATOR NAME ROLE', + [keccakRole('ADD_NODE_OPERATOR_ROLE')]: 'ADD NODE OPERATOR ROLE', + [keccakRole('REPORT_STOPPED_VALIDATORS_ROLE')]: + 'REPORT STOPPED VALIDATORS ROLE', + [keccakRole('SET_NODE_OPERATOR_ACTIVE_ROLE')]: + 'SET NODE OPERATOR ACTIVE ROLE', + [keccakRole('SET_NODE_OPERATOR_LIMIT_ROLE')]: 'SET NODE OPERATOR LIMIT ROLE', + [keccakRole('MANAGE_QUORUM')]: 'MANAGE QUORUM', + [keccakRole('SET_BEACON_REPORT_RECEIVER')]: 'SET BEACON REPORT RECEIVER', + [keccakRole('MANAGE_MEMBERS')]: 'MANAGE MEMBERS', + [keccakRole('SET_BEACON_SPEC')]: 'SET BEACON SPEC', + [keccakRole('SET_REPORT_BOUNDARIES')]: 'SET REPORT BOUNDARIES', + [keccakRole('RESUME_ROLE')]: 'RESUME ROLE', + [keccakRole('STAKING_PAUSE_ROLE')]: 'STAKING PAUSE ROLE', + [keccakRole('STAKING_CONTROL_ROLE')]: 'STAKING CONTROL ROLE', + [keccakRole('MANAGE_PROTOCOL_CONTRACTS_ROLE')]: + 'MANAGE PROTOCOL CONTRACTS ROLE', + [keccakRole('SET_EL_REWARDS_VAULT_ROLE')]: 'SET EL REWARDS VAULT ROLE', + [keccakRole('SET_EL_REWARDS_WITHDRAWAL_LIMIT_ROLE')]: + 'SET EL REWARDS WITHDRAWAL LIMIT ROLE', + [keccakRole('DEPOSIT_ROLE')]: 'DEPOSIT ROLE', + [keccakRole('STAKING_ROUTER_ROLE')]: 'STAKING ROUTER ROLE', + [keccakRole('MANAGE_SIGNING_KEYS')]: 'MANAGE SIGNING KEYS', + [keccakRole('STAKING_MODULE_PAUSE_ROLE')]: 'STAKING MODULE PAUSE ROLE', + [keccakRole('STAKING_MODULE_RESUME_ROLE')]: 'STAKING MODULE RESUME ROLE', + [keccakRole('STAKING_MODULE_UNVETTING_ROLE')]: + 'STAKING MODULE UNVETTING ROLE', + [keccakRole('MANAGE_CONSENSUS_VERSION_ROLE')]: + 'MANAGE CONSENSUS VERSION ROLE', + [keccakRole('REQUEST_BURN_SHARES_ROLE')]: 'REQUEST BURN SHARES ROLE', + [keccakRole('MANAGE_MEMBERS_AND_QUORUM_ROLE')]: + 'MANAGE MEMBERS AND QUORUM ROLE', + [keccakRole('DEFAULT_ADMIN_ROLE')]: 'DEFAULT ADMIN ROLE', + [keccakRole('CONFIG_MANAGER_ROLE')]: 'CONFIG MANAGER ROLE', + [keccakRole('MANAGE_FAST_LANE_CONFIG_ROLE')]: 'MANAGE FAST LANE CONFIG ROLE', + [keccakRole('MANAGE_REPORT_PROCESSOR_ROLE')]: 'MANAGE REPORT PROCESSOR ROLE', + [keccakRole('MANAGE_FRAME_CONFIG_ROLE')]: 'MANAGE FRAME CONFIG ROLE', + [keccakRole('DISABLE_CONSENSUS_ROLE')]: 'DISABLE CONSENSUS ROLE', + [keccakRole('MANAGE_CONSENSUS_CONTRACT_ROLE')]: + 'MANAGE CONSENSUS CONTRACT ROLE', + [keccakRole('SUBMIT_DATA_ROLE')]: 'SUBMIT DATA ROLE', + [keccakRole('PAUSE_ROLE')]: 'PAUSE ROLE', + [keccakRole('REQUEST_BURN_MY_STETH_ROLE')]: 'REQUEST BURN MY STETH ROLE', + [keccakRole('RECOVER_ASSETS_ROLE')]: 'RECOVER ASSETS ROLE', + [keccakRole('ALL_LIMITS_MANAGER_ROLE')]: 'ALL LIMITS MANAGER ROLE', + [keccakRole('CHURN_VALIDATORS_PER_DAY_LIMIT_MANGER_ROLE')]: + 'CHURN VALIDATORS PER DAY LIMIT MANGER ROLE', + [keccakRole('ONE_OFF_CL_BALANCE_DECREASE_LIMIT_MANAGER_ROLE')]: + 'ONE OFF CL BALANCE DECREASE LIMIT MANAGER ROLE', + [keccakRole('ANNUAL_BALANCE_INCREASE_LIMIT_MANAGER_ROLE')]: + 'ANNUAL BALANCE INCREASE LIMIT MANAGER ROLE', + [keccakRole('SHARE_RATE_DEVIATION_LIMIT_MANAGER_ROLE')]: + 'SHARE RATE DEVIATION LIMIT MANAGER ROLE', + [keccakRole('MAX_VALIDATOR_EXIT_REQUESTS_PER_REPORT_ROLE')]: + 'MAX VALIDATOR EXIT REQUESTS PER REPORT ROLE', + [keccakRole('MAX_ACCOUNTING_EXTRA_DATA_LIST_ITEMS_COUNT_ROLE')]: + 'MAX ACCOUNTING EXTRA DATA LIST ITEMS COUNT ROLE', + [keccakRole('MAX_NODE_OPERATORS_PER_EXTRA_DATA_ITEM_COUNT_ROLE')]: + 'MAX NODE OPERATORS PER EXTRA DATA ITEM COUNT ROLE', + [keccakRole('REQUEST_TIMESTAMP_MARGIN_MANAGER_ROLE')]: + 'REQUEST TIMESTAMP MARGIN MANAGER ROLE', + [keccakRole('MAX_POSITIVE_TOKEN_REBASE_MANAGER_ROLE')]: + 'MAX POSITIVE TOKEN REBASE MANAGER ROLE', + [keccakRole('APPEARED_VALIDATORS_PER_DAY_LIMIT_MANAGER_ROLE')]: + 'APPEARED VALIDATORS PER DAY LIMIT MANAGER ROLE', + [keccakRole('EXITED_VALIDATORS_PER_DAY_LIMIT_MANAGER_ROLE')]: + 'EXITED VALIDATORS PER DAY LIMIT MANAGER ROLE', + [keccakRole('ACCOUNTING_MANAGER_ROLE')]: 'ACCOUNTING MANAGER ROLE', + [keccakRole('FINALIZE_ROLE')]: 'FINALIZE ROLE', + [keccakRole('ORACLE_ROLE')]: 'ORACLE ROLE', + [keccakRole('MANAGE_TOKEN_URI_ROLE')]: 'MANAGE TOKEN URI ROLE', + [keccakRole('MANAGE_WITHDRAWAL_CREDENTIALS_ROLE')]: + 'MANAGE WITHDRAWAL CREDENTIALS ROLE', + [keccakRole('STAKING_MODULE_MANAGE_ROLE')]: 'STAKING MODULE MANAGE ROLE', + [keccakRole('REPORT_EXITED_VALIDATORS_ROLE')]: + 'REPORT EXITED VALIDATORS ROLE', + [keccakRole('UNSAFE_SET_EXITED_VALIDATORS_ROLE')]: + 'UNSAFE SET EXITED VALIDATORS ROLE', + [keccakRole('REPORT_REWARDS_MINTED_ROLE')]: 'REPORT REWARDS MINTED ROLE', + [keccakRole('RECOVERER_ROLE')]: 'RECOVERER ROLE', + [keccakRole('RESET_BOND_CURVE_ROLE')]: 'RESET BOND CURVE ROLE', + [keccakRole('SET_BOND_CURVE_ROLE')]: 'SET BOND CURVE ROLE', + [keccakRole('MANAGE_BOND_CURVES_ROLE')]: 'MANAGE BOND CURVES ROLE', + [keccakRole('CONTRACT_MANAGER_ROLE')]: 'CONTRACT MANAGER ROLE', +} diff --git a/modules/votes/providers/VoteFormActions/VoteFormActionsContext.tsx b/modules/votes/providers/VoteFormActions/VoteFormActionsContext.tsx index 7e1ffe2b..f7767cca 100644 --- a/modules/votes/providers/VoteFormActions/VoteFormActionsContext.tsx +++ b/modules/votes/providers/VoteFormActions/VoteFormActionsContext.tsx @@ -14,20 +14,18 @@ import { useFormVoteInfo } from 'modules/votes/ui/VoteForm/useFormVoteInfo' import { useGovernanceSymbol } from 'modules/tokens/hooks/useGovernanceSymbol' import { ResultTx } from 'modules/blockChain/types' -import { VoteMode, VotePhase } from 'modules/votes/types' +import { CastVoteEvent, VoteMode, VotePhase } from 'modules/votes/types' import invariant from 'tiny-invariant' -import { - AttemptCastVoteAsDelegateEventObject, - CastVoteEventObject, -} from 'generated/AragonVotingAbi' +import { AttemptCastVoteAsDelegateEventObject } from 'generated/AragonVotingAbi' import { BigNumber } from '@ethersproject/bignumber' import { FinishHandler, TransactionSender, } from 'modules/blockChain/hooks/useTransactionSender' import { useDelegationInfo } from 'modules/delegation/hooks/useDelegationInfo' +import { DelegationInfo } from 'modules/delegation/types' export enum VotedAs { delegate = 'delegate', @@ -40,7 +38,7 @@ export type VoteFormActionsContextValue = { setSuccessTx: React.Dispatch> formVoteSubmitData: ReturnType setVoteId: React.Dispatch> - votePower: Number | undefined + votePower: number | undefined handleVote: (mode: VoteMode | null) => Promise handleDelegatesVote: ( mode: VoteMode | null, @@ -50,18 +48,18 @@ export type VoteFormActionsContextValue = { mode: VoteMode | null txVote: TransactionSender txDelegatesVote: TransactionSender - eventsVoted: CastVoteEventObject[] | undefined + eventsVoted: CastVoteEvent[] | undefined eventsDelegatesVoted: AttemptCastVoteAsDelegateEventObject[] | undefined eligibleDelegatedVotingPower: BigNumber delegatedVotersAddresses: string[] eligibleDelegatedVoters: EligibleDelegatorsData['eligibleDelegatedVoters'] eligibleDelegatedVotersAddresses: string[] - delegatorsVotedThemselves: CastVoteEventObject[] | undefined + delegatorsVotedThemselves: CastVoteEvent[] | undefined governanceSymbol: string | undefined votedByDelegate: EligibleDelegatorsData['eligibleDelegatedVoters'] voterState: number | null | undefined isSubmitting: false | VoteMode - delegationInfo: Record | undefined + delegationInfo: DelegationInfo | undefined votePhase: VotePhase | undefined } diff --git a/modules/votes/types.ts b/modules/votes/types.ts index f3ae4133..cb1aa52f 100644 --- a/modules/votes/types.ts +++ b/modules/votes/types.ts @@ -1,3 +1,4 @@ +import { BigNumber } from 'ethers' import { AragonVotingAbi } from 'generated' import { UnwrapPromise } from 'next/dist/lib/coalesced-function' @@ -33,3 +34,11 @@ export enum VotePhase { Objection, Closed, } + +export type CastVoteEvent = { + voter: string + supports: boolean + stake: BigNumber + blockNumber: number + transactionIndex: number +} diff --git a/modules/votes/ui/VoteActionsModals/DelegatorsList/VoteActionsDelegatorsList.tsx b/modules/votes/ui/VoteActionsModals/DelegatorsList/VoteActionsDelegatorsList.tsx index 47d92f64..47acabbf 100644 --- a/modules/votes/ui/VoteActionsModals/DelegatorsList/VoteActionsDelegatorsList.tsx +++ b/modules/votes/ui/VoteActionsModals/DelegatorsList/VoteActionsDelegatorsList.tsx @@ -16,13 +16,13 @@ import { formatBalance } from 'modules/blockChain/utils/formatBalance' import { useSimpleReducer } from 'modules/shared/hooks/useSimpleReducer' import { BigNumber } from 'ethers' import { EligibleDelegator } from 'modules/delegation/hooks/useEligibleDelegators' -import { CastVoteEventObject } from 'generated/AragonVotingAbi' +import { CastVoteEvent } from 'modules/votes/types' interface Props { - delegatorsVotedThemselves: CastVoteEventObject[] | undefined + delegatorsVotedThemselves: CastVoteEvent[] | undefined governanceSymbol: string | undefined onSelectedAddressesChange: (address: string[]) => void - eventsVoted: CastVoteEventObject[] | undefined + eventsVoted: CastVoteEvent[] | undefined eligibleDelegatedVoters: EligibleDelegator[] eligibleDelegatedVotingPower: BigNumber defaultExpanded: boolean diff --git a/modules/votes/ui/VoteActionsModals/SuccessModal/VoteSuccessModal.tsx b/modules/votes/ui/VoteActionsModals/SuccessModal/VoteSuccessModal.tsx index 35878ee2..b8dc72ea 100644 --- a/modules/votes/ui/VoteActionsModals/SuccessModal/VoteSuccessModal.tsx +++ b/modules/votes/ui/VoteActionsModals/SuccessModal/VoteSuccessModal.tsx @@ -37,14 +37,15 @@ import { TxRow } from 'modules/blockChain/ui/TxRow' import { openWindow } from 'modules/shared/utils/openWindow' import { VoteActionButtonObjectionTooltip } from 'modules/votes/ui/VoteActionButtonObjectionTooltip' import { EligibleDelegator } from 'modules/delegation/hooks/useEligibleDelegators' +import { DelegationInfo } from 'modules/delegation/types' interface SnapshotData { mode: 'yay' | 'nay' | 'enact' | null eligibleDelegatedVotingPower: BigNumber votePhase: VotePhase | undefined votedByDelegate: EligibleDelegator[] - votePower: Number | undefined - delegationInfo: Record | undefined + votePower: number | undefined + delegationInfo: DelegationInfo | undefined delegatedVotersAddresses: string[] eligibleDelegatedVoters: EligibleDelegator[] voterState: number | null | undefined diff --git a/modules/votes/ui/VoteDetails/VoteDetails.tsx b/modules/votes/ui/VoteDetails/VoteDetails.tsx index cfa11aac..942eb901 100644 --- a/modules/votes/ui/VoteDetails/VoteDetails.tsx +++ b/modules/votes/ui/VoteDetails/VoteDetails.tsx @@ -1,9 +1,6 @@ import { useMemo } from 'react' import { Text } from '@lidofinance/lido-ui' -import type { - AttemptCastVoteAsDelegateEventObject, - CastVoteEventObject, -} from 'generated/AragonVotingAbi' +import type { AttemptCastVoteAsDelegateEventObject } from 'generated/AragonVotingAbi' import { VoteScript } from '../VoteScript' import { VoteYesNoBar } from '../VoteYesNoBar' import { @@ -17,7 +14,7 @@ import { } from './VoteDetailsStyle' import { VoteDescription } from '../VoteDescription' -import { Vote, VotePhase, VoteStatus } from 'modules/votes/types' +import { CastVoteEvent, Vote, VotePhase, VoteStatus } from 'modules/votes/types' import { weiToNum } from 'modules/blockChain/utils/parseWei' import { getVoteDetailsFormatted } from 'modules/votes/utils/getVoteDetailsFormatted' import { VoteStatusChips } from '../VoteStatusChips' @@ -32,7 +29,7 @@ type Props = { objectionPhaseTime: number metadata?: string isEnded: boolean - eventsVoted: CastVoteEventObject[] | undefined + eventsVoted: CastVoteEvent[] | undefined executedTxHash?: string eventsDelegatesVoted: AttemptCastVoteAsDelegateEventObject[] | undefined votePhase: VotePhase | undefined diff --git a/modules/votes/ui/VoteForm/useFormVoteInfo.ts b/modules/votes/ui/VoteForm/useFormVoteInfo.ts index e5a3adc1..2973ebdd 100644 --- a/modules/votes/ui/VoteForm/useFormVoteInfo.ts +++ b/modules/votes/ui/VoteForm/useFormVoteInfo.ts @@ -51,9 +51,13 @@ export function useFormVoteInfo({ voteId }: Args) { ]) const snapshotBlock = vote.snapshotBlock.toNumber() + const eventsVoted = await getEventsCastVote( + contractVoting, + _voteId, + snapshotBlock, + ) const [ eventStart, - eventsVoted, eventsDelegatesVoted, eventExecuteVote, canVote, @@ -61,9 +65,9 @@ export function useFormVoteInfo({ voteId }: Args) { votePowerWei, ] = await Promise.all([ getEventStartVote(contractVoting, _voteId, snapshotBlock), - getEventsCastVote(contractVoting, _voteId, snapshotBlock), getEventsAttemptCastVoteAsDelegate( contractVoting, + eventsVoted, _voteId, snapshotBlock, ), diff --git a/modules/votes/ui/VoteFormActions/VoteFormActions.tsx b/modules/votes/ui/VoteFormActions/VoteFormActions.tsx index 9462a70e..88c13637 100644 --- a/modules/votes/ui/VoteFormActions/VoteFormActions.tsx +++ b/modules/votes/ui/VoteFormActions/VoteFormActions.tsx @@ -28,7 +28,7 @@ type Props = { onEnact: () => void voteId: string votePowerWei: BigNumber | null | undefined - votePower: Number | undefined + votePower: number | undefined } type ModalData = { diff --git a/modules/votes/ui/VoteInfoDelegated/VoteInfoDelegated.tsx b/modules/votes/ui/VoteInfoDelegated/VoteInfoDelegated.tsx index 6ca75c04..7655c75a 100644 --- a/modules/votes/ui/VoteInfoDelegated/VoteInfoDelegated.tsx +++ b/modules/votes/ui/VoteInfoDelegated/VoteInfoDelegated.tsx @@ -1,18 +1,19 @@ -import { Text } from '@lidofinance/lido-ui' -import { AddressBadge } from 'modules/shared/ui/Common/AddressBadge' +import { Identicon, Text, trimAddress } from '@lidofinance/lido-ui' import { InfoWrap, VoteStatus, -} from 'modules/votes/ui/VoteInfoDelegated/VoteInfoDelegatedStyle' + AddressBadgeWrap, +} from './VoteInfoDelegatedStyle' import { useDelegateVoteInfo } from 'modules/delegation/hooks/useDelegateVoteInfo' -import type { - AttemptCastVoteAsDelegateEventObject, - CastVoteEventObject, -} from 'generated/AragonVotingAbi' +import type { AttemptCastVoteAsDelegateEventObject } from 'generated/AragonVotingAbi' +import { CastVoteEvent } from 'modules/votes/types' +import { getPublicDelegateByAddress } from 'modules/delegation/utils/getPublicDelegateName' +import { AddressPop } from 'modules/shared/ui/Common/AddressPop' +import { PublicDelegateAvatar } from 'modules/delegation/ui/PublicDelegateAvatar' interface Props { walletAddress: string | null | undefined - eventsVoted: CastVoteEventObject[] | undefined + eventsVoted: CastVoteEvent[] | undefined eventsDelegatesVoted: AttemptCastVoteAsDelegateEventObject[] | undefined } @@ -31,12 +32,29 @@ export function VoteInfoDelegated({ return null } + const publicDelegate = getPublicDelegateByAddress(voteInfo.delegate) + return ( Delegate - + + + {publicDelegate ? ( + + ) : ( + + )} + + {publicDelegate?.name ?? trimAddress(voteInfo.delegate, 4)} + + + voted diff --git a/modules/votes/ui/VoteInfoDelegated/VoteInfoDelegatedStyle.tsx b/modules/votes/ui/VoteInfoDelegated/VoteInfoDelegatedStyle.tsx index 938c184b..4f84eac4 100644 --- a/modules/votes/ui/VoteInfoDelegated/VoteInfoDelegatedStyle.tsx +++ b/modules/votes/ui/VoteInfoDelegated/VoteInfoDelegatedStyle.tsx @@ -26,3 +26,17 @@ export const VoteStatus = styled.div` line-height: 1; } ` + +export const AddressBadgeWrap = styled.span` + display: inline-flex; + vertical-align: middle; + align-items: center; + justify-content: center; + padding: 2px 10px 2px 4px; + width: fit-content; + border-radius: ${({ theme }) => theme.borderRadiusesMap.xl}px; + background-color: var(--lido-color-backgroundSecondary); + & > div:nth-child(1) { + margin-right: 8px; + } +` diff --git a/modules/votes/ui/VoteScript/VoteScriptBody.tsx b/modules/votes/ui/VoteScript/VoteScriptBody.tsx index 2511b763..26ab33c2 100644 --- a/modules/votes/ui/VoteScript/VoteScriptBody.tsx +++ b/modules/votes/ui/VoteScript/VoteScriptBody.tsx @@ -35,7 +35,7 @@ export function VoteScriptBody({ binary, decoded, parentId }: Props) { {decoded.calls.map((call, i) => { const id = i + 1 const { address, abi, encodedCallData, decodedCallData } = call - const callString = formatCallString(id, abi, decodedCallData) + const callString = formatCallString(chainId, id, abi, decodedCallData) const nestedScriptsIdxs = abi?.inputs?.reduce( (r, c, j) => (c.name === '_evmScript' ? [...r, j] : r), [], diff --git a/modules/votes/ui/VoteScript/utils.tsx b/modules/votes/ui/VoteScript/utils.tsx index af80acde..f9d6e300 100644 --- a/modules/votes/ui/VoteScript/utils.tsx +++ b/modules/votes/ui/VoteScript/utils.tsx @@ -2,8 +2,13 @@ import { ABIElement, EVMScriptDecoded, } from '@lidofinance/evm-script-decoder/lib/types' +import { LIDO_ROLES } from 'modules/votes/constants' +import { utils } from 'ethers' +import { getContractName } from 'modules/config/utils/getContractName' +import { CHAINS } from '@lido-sdk/constants' export const formatCallString = ( + chainId: CHAINS, id: number, abi?: ABIElement, callData?: (string | EVMScriptDecoded)[], @@ -32,6 +37,17 @@ export const formatCallString = ( ) { callRes += `See parsed evm script at ${id}.${i + 1}` } else { + if (typeof data === 'string') { + const roleLabel = LIDO_ROLES[data] + if (roleLabel) { + callRes += `[${roleLabel}] ` + } else if (utils.isAddress(data)) { + const contractName = getContractName(chainId, data) + if (contractName) { + callRes += `[${contractName}] ` + } + } + } callRes += data } return callRes diff --git a/modules/votes/ui/VoteVotersList/VoteVotersList.tsx b/modules/votes/ui/VoteVotersList/VoteVotersList.tsx index ed0cf078..747d55ce 100644 --- a/modules/votes/ui/VoteVotersList/VoteVotersList.tsx +++ b/modules/votes/ui/VoteVotersList/VoteVotersList.tsx @@ -9,21 +9,20 @@ import { ListRow, ListRowCell, AddressWrap, - Identicon, ShowMoreBtn, } from './VoteVotersListStyle' -import { Tooltip, trimAddress, Text } from '@lidofinance/lido-ui' +import { Tooltip, trimAddress, Text, Identicon } from '@lidofinance/lido-ui' import { weiToNum } from 'modules/blockChain/utils/parseWei' import { formatNumber } from 'modules/shared/utils/formatNumber' -import type { - CastVoteEventObject, - AttemptCastVoteAsDelegateEventObject, -} from 'generated/AragonVotingAbi' +import type { AttemptCastVoteAsDelegateEventObject } from 'generated/AragonVotingAbi' import { formatBalance } from 'modules/blockChain/utils/formatBalance' +import { getPublicDelegateByAddress } from 'modules/delegation/utils/getPublicDelegateName' +import { PublicDelegateAvatar } from 'modules/delegation/ui/PublicDelegateAvatar' +import { CastVoteEvent } from 'modules/votes/types' type Props = { - eventsVoted: CastVoteEventObject[] + eventsVoted: CastVoteEvent[] eventsDelegatesVoted: AttemptCastVoteAsDelegateEventObject[] | undefined } @@ -94,6 +93,8 @@ export function VoteVotersList({ eventsVoted, eventsDelegatesVoted }: Props) { const delegateAddress = delegateVotesMap.get(event.voter) || null const votedByDelegate = !!delegateAddress + const publicDelegate = getPublicDelegateByAddress(event.voter) + return ( @@ -101,12 +102,23 @@ export function VoteVotersList({ eventsVoted, eventsDelegatesVoted }: Props) { address={event.voter} delegateAddress={delegateAddress} > - - - {(ensNameList && ensNameList[i]) || - trimAddress(event.voter, 4)} - {votedByDelegate && } - + {publicDelegate ? ( + + + {publicDelegate.name} + {votedByDelegate && } + + ) : ( + + + {(ensNameList && ensNameList[i]) || + trimAddress(event.voter, 4)} + {votedByDelegate && } + + )} {event.supports ? 'Yes' : 'No'} diff --git a/modules/votes/ui/VoteVotersList/VoteVotersListStyle.ts b/modules/votes/ui/VoteVotersList/VoteVotersListStyle.ts index fdd18b5d..aa8c4ede 100644 --- a/modules/votes/ui/VoteVotersList/VoteVotersListStyle.ts +++ b/modules/votes/ui/VoteVotersList/VoteVotersListStyle.ts @@ -1,5 +1,4 @@ import styled from 'styled-components' -import { Identicon as IdenticonOriginal } from '@lidofinance/lido-ui' export const Wrap = styled.div` margin-top: ${({ theme }) => theme.spaceMap.lg}px; @@ -53,12 +52,7 @@ export const ListRowCell = styled.div` export const AddressWrap = styled.div` display: flex; align-items: center; - gap: 8px; -` - -export const Identicon = styled(IdenticonOriginal)` - flex: 0 0 auto; - margin-right: ${({ theme }) => theme.spaceMap.sm}px; + gap: ${({ theme }) => theme.spaceMap.md}px; ; ` export const ShowMoreBtn = styled(ListRow)` diff --git a/modules/votes/utils/getEventsCastVote.ts b/modules/votes/utils/getEventsCastVote.ts index 5420014e..fe9ec49c 100644 --- a/modules/votes/utils/getEventsCastVote.ts +++ b/modules/votes/utils/getEventsCastVote.ts @@ -1,7 +1,7 @@ import type { AragonVotingAbi } from 'generated' -import type { CastVoteEventObject } from 'generated/AragonVotingAbi' +import { CastVoteEvent } from '../types' -export function unifyEventsVotedWithLast(events: CastVoteEventObject[]) { +export function unifyEventsVotedWithLast(events: CastVoteEvent[]) { return events.reverse().reduce( (all, curr) => { const voter = curr.voter @@ -13,7 +13,7 @@ export function unifyEventsVotedWithLast(events: CastVoteEventObject[]) { }, { already: {} as Record, - res: [] as CastVoteEventObject[], + res: [] as CastVoteEvent[], }, ).res } @@ -28,12 +28,20 @@ export async function getEventsCastVote( filter, block ? Number(block) : undefined, ) - const decoded = events.map(e => e.args) as CastVoteEventObject[] + const decoded = events.map(e => ({ + blockNumber: e.blockNumber, + transactionIndex: e.transactionIndex, + voter: e.args.voter, + supports: e.args.supports, + stake: e.args.stake, + })) + return unifyEventsVotedWithLast(decoded) } export async function getEventsAttemptCastVoteAsDelegate( contractVoting: AragonVotingAbi, + castVoteEvents: CastVoteEvent[], voteId: string | number, block?: string | number, ) { @@ -45,12 +53,6 @@ export async function getEventsAttemptCastVoteAsDelegate( block ? Number(block) : undefined, ) - const castVoteFilter = contractVoting.filters.CastVote(Number(voteId)) - const castVoteEvents = await contractVoting.queryFilter( - castVoteFilter, - block ? Number(block) : undefined, - ) - const voterToLatestVote = new Map< string, { @@ -82,7 +84,7 @@ export async function getEventsAttemptCastVoteAsDelegate( }) castVoteEvents.forEach(event => { - const voterLower = event.args.voter.toLowerCase() + const voterLower = event.voter.toLowerCase() const existing = voterToLatestVote.get(voterLower) if ( !existing || diff --git a/next.config.mjs b/next.config.mjs index 9faef0a4..4550934c 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -1,16 +1,14 @@ const basePath = process.env.BASE_PATH || '' const rpcUrls_1 = - (process.env.EL_RPC_URLS_1 && process.env.EL_RPC_URLS_1.split(',')) -const rpcUrls_5 = - process.env.EL_RPC_URLS_5 && process.env.EL_RPC_URLS_5.split(',') + process.env.EL_RPC_URLS_1 && process.env.EL_RPC_URLS_1.split(',') const rpcUrls_17000 = process.env.EL_RPC_URLS_17000 && process.env.EL_RPC_URLS_17000.split(',') const etherscanApiKey = process.env.ETHERSCAN_API_KEY // Mainnet is the default chain -const _defaultChain = '1'; +const _defaultChain = '1' // Keep both Mainnet and Holesky as defaults const defaultSupportedChains = '1,17000' @@ -113,7 +111,6 @@ export default { serverRuntimeConfig: { basePath, rpcUrls_1, - rpcUrls_5, rpcUrls_17000, etherscanApiKey, cspTrustedHosts, diff --git a/pages/api/rpc.ts b/pages/api/rpc.ts index 88fcbe9e..8ee9ab35 100644 --- a/pages/api/rpc.ts +++ b/pages/api/rpc.ts @@ -10,7 +10,7 @@ import { AragonVoting } from '../../modules/blockChain/contractAddresses' import { AragonVotingAbi__factory } from '../../generated' const { serverRuntimeConfig } = getConfig() -const { rpcUrls_1, rpcUrls_5, rpcUrls_17000 } = serverRuntimeConfig +const { rpcUrls_1, rpcUrls_17000 } = serverRuntimeConfig interface IRpcRequest { method: string @@ -53,7 +53,6 @@ const hasVoteId = (item: any) => !isNaN(item.voteId) export default async function rpc(req: NextApiRequest, res: NextApiResponse) { const RPC_URLS: Record = { [CHAINS.Mainnet]: rpcUrls_1, - [CHAINS.Goerli]: rpcUrls_5, [CHAINS.Holesky]: rpcUrls_17000, } diff --git a/pages/delegation/[[...mode]].tsx b/pages/delegation/[[...mode]].tsx index c1a50534..d38b5bb3 100644 --- a/pages/delegation/[[...mode]].tsx +++ b/pages/delegation/[[...mode]].tsx @@ -21,13 +21,14 @@ const DelegationPage: FC = ({ mode }) => { export default DelegationPage type DelegationModePageParams = { - mode: ['delegators'] | undefined + mode: ['delegators'] | ['customize'] | undefined } export const getStaticPaths: GetStaticPaths = () => { return { paths: [ { params: { mode: undefined } }, + { params: { mode: ['customize'] } }, { params: { mode: ['delegators'] } }, ], fallback: false, // return 404 on non match @@ -42,5 +43,5 @@ export const getStaticProps: GetStaticProps< const mode = params?.mode if (!mode) return { props: { mode: 'delegation' }, revalidate: 60 } - return { props: { mode: 'delegators' }, revalidate: 60 } + return { props: { mode: mode[0] }, revalidate: 60 } } diff --git a/public/delegates/degentrading.jpg b/public/delegates/degentrading.jpg new file mode 100644 index 00000000..8ea775b9 Binary files /dev/null and b/public/delegates/degentrading.jpg differ