From 9e55b052cdd19249f59cba4a6ac816a9b168b9a7 Mon Sep 17 00:00:00 2001 From: Rami Date: Sun, 23 Mar 2025 22:54:53 -0300 Subject: [PATCH 01/58] Add separate wfs for hoodi and holesky --- .github/workflows/ci-dev-holesky.yml | 29 ++++++++++++++++++++++++++++ .github/workflows/ci-dev.yml | 4 ++-- 2 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/ci-dev-holesky.yml diff --git a/.github/workflows/ci-dev-holesky.yml b/.github/workflows/ci-dev-holesky.yml new file mode 100644 index 00000000..ad534f45 --- /dev/null +++ b/.github/workflows/ci-dev-holesky.yml @@ -0,0 +1,29 @@ +name: CI Dev Holesky + +on: + workflow_dispatch: + push: + branches: + - holesky + 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_holesky_testnet_csm_widget.yaml" + TARGET: "holesky" diff --git a/.github/workflows/ci-dev.yml b/.github/workflows/ci-dev.yml index dfe72ff2..25e81cf3 100644 --- a/.github/workflows/ci-dev.yml +++ b/.github/workflows/ci-dev.yml @@ -1,4 +1,4 @@ -name: CI Dev +name: CI Dev Hoodi on: workflow_dispatch: @@ -25,5 +25,5 @@ jobs: APP_ID: ${{ secrets.APP_ID }} APP_PRIVATE_KEY: ${{ secrets.APP_PRIVATE_KEY }} TARGET_REPO: "lidofinance/infra-mainnet" - TARGET_WORKFLOW: "deploy_testnet_csm_widget.yaml" + TARGET_WORKFLOW: "deploy_hoodi_testnet_csm_widget.yaml" TARGET: "develop" From 9cf89e8cc9b8113b9ec457eba5663a52b8962aba Mon Sep 17 00:00:00 2001 From: exromany Date: Tue, 18 Mar 2025 07:37:15 +0300 Subject: [PATCH 02/58] fix: missed imports --- utilsApi/api/apiProxyFactory.ts | 14 +++++++------- utilsApi/api/errors.ts | 15 +++++++++++++++ 2 files changed, 22 insertions(+), 7 deletions(-) create mode 100644 utilsApi/api/errors.ts diff --git a/utilsApi/api/apiProxyFactory.ts b/utilsApi/api/apiProxyFactory.ts index fd0833c5..163ce394 100644 --- a/utilsApi/api/apiProxyFactory.ts +++ b/utilsApi/api/apiProxyFactory.ts @@ -1,17 +1,17 @@ import type { ServerLogger } from '@lidofinance/api-logger'; -import { - DEFAULT_API_ERROR_MESSAGE, - HEALTHY_RPC_SERVICES_ARE_OVER, - RpcProviders, - UnsupportedChainIdError, - UnsupportedHTTPMethodError, -} from '@lidofinance/next-pages'; +import { RpcProviders } from '@lidofinance/next-pages'; import { iterateUrls } from '@lidofinance/rpc'; import type { NextApiRequest, NextApiResponse } from 'next'; import { Readable } from 'node:stream'; import { ReadableStream } from 'node:stream/web'; import { Counter, Registry } from 'prom-client'; import type { TrackedFetchApi } from './trackedFetchApiFactory'; +import { + HEALTHY_RPC_SERVICES_ARE_OVER, + UnsupportedChainIdError, + UnsupportedHTTPMethodError, +} from './errors'; +import { DEFAULT_API_ERROR_MESSAGE } from '@lidofinance/next-api-wrapper'; export type ApiFactoryParams = { metrics: { diff --git a/utilsApi/api/errors.ts b/utilsApi/api/errors.ts new file mode 100644 index 00000000..215a1bfc --- /dev/null +++ b/utilsApi/api/errors.ts @@ -0,0 +1,15 @@ +export const HEALTHY_RPC_SERVICES_ARE_OVER = 'Healthy RPC services are over!'; + +export class ClientError extends Error {} + +export class UnsupportedChainIdError extends ClientError { + constructor(message?: string) { + super(message || 'Unsupported chainId'); + } +} + +export class UnsupportedHTTPMethodError extends ClientError { + constructor(message?: string) { + super(message || 'Unsupported HTTP method'); + } +} From 6b0852309a19a764e6ce99634ddd04e6c32524d2 Mon Sep 17 00:00:00 2001 From: exromany Date: Tue, 18 Mar 2025 07:11:10 +0300 Subject: [PATCH 03/58] chore: drop unused subgraph code chore: drop consts --- config/get-secret-config.ts | 5 ----- config/groups/cache.ts | 4 ---- consts/aggregator.ts | 19 ------------------- consts/api.ts | 5 ----- consts/metrics.ts | 1 - consts/tx.ts | 10 ---------- global.d.ts | 11 ----------- next.config.mjs | 11 ----------- types/index.ts | 1 - types/subgraph.ts | 3 --- utilsApi/getSubgraphUrl.ts | 12 ------------ utilsApi/index.ts | 1 - utilsApi/metrics/metrics.ts | 2 -- utilsApi/metrics/subgraph.ts | 22 ---------------------- 14 files changed, 107 deletions(-) delete mode 100644 consts/aggregator.ts delete mode 100644 consts/tx.ts delete mode 100644 types/subgraph.ts delete mode 100644 utilsApi/getSubgraphUrl.ts delete mode 100644 utilsApi/metrics/subgraph.ts diff --git a/config/get-secret-config.ts b/config/get-secret-config.ts index ca193c78..84d5e13d 100644 --- a/config/get-secret-config.ts +++ b/config/get-secret-config.ts @@ -16,8 +16,6 @@ export type SecretConfigType = Modify< cspReportOnly: boolean; - subgraphRequestTimeout: number; - rateLimit: number; rateLimitTimeFrame: number; } @@ -54,9 +52,6 @@ export const getSecretConfig = (): SecretConfigType => { cspReportOnly: toBoolean(serverRuntimeConfig.cspReportOnly), - subgraphRequestTimeout: - Number(serverRuntimeConfig.subgraphRequestTimeout) || 5000, - rateLimit: Number(serverRuntimeConfig.rateLimit) || 100, rateLimitTimeFrame: Number(serverRuntimeConfig.rateLimitTimeFrame) || 60, // 1 minute; }; diff --git a/config/groups/cache.ts b/config/groups/cache.ts index 9d0a050e..b71130fc 100644 --- a/config/groups/cache.ts +++ b/config/groups/cache.ts @@ -15,10 +15,6 @@ export const CACHE_LIDO_STATS_TTL = ms('1h'); export const CACHE_LIDO_SHORT_STATS_KEY = 'cache-short-lido-stats'; export const CACHE_LIDO_SHORT_STATS_TTL = ms('1h'); -export const CACHE_LIDO_HOLDERS_VIA_SUBGRAPHS_KEY = - 'cache-lido-holders-via-subgraphs'; -export const CACHE_LIDO_HOLDERS_VIA_SUBGRAPHS_TTL = ms('7d'); - export const CACHE_LDO_STATS_KEY = 'cache-ldo-stats'; export const CACHE_LDO_STATS_TTL = ms('1h'); diff --git a/consts/aggregator.ts b/consts/aggregator.ts deleted file mode 100644 index 5da55d34..00000000 --- a/consts/aggregator.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { CHAINS } from '@lido-sdk/constants'; -import invariant from 'tiny-invariant'; - -// https://etherscan.io/address/0xcfe54b5cd566ab89272946f602d76ea879cab4a8 -export const AGGREGATOR_STETH_USD_PRICE_FEED_BY_NETWORK: { - [key in CHAINS]?: string; -} = { - [CHAINS.Mainnet]: '0xcfe54b5cd566ab89272946f602d76ea879cab4a8', -}; - -// Chainlink: STETH/USD Price Feed -// https://data.chain.link/ethereum/mainnet/crypto-usd/steth-usd -export const getAggregatorStEthUsdPriceFeedAddress = ( - chainId: CHAINS, -): string => { - const address = AGGREGATOR_STETH_USD_PRICE_FEED_BY_NETWORK[chainId]; - invariant(address, 'chain is not supported'); - return address; -}; diff --git a/consts/api.ts b/consts/api.ts index 45256248..0c8e3eae 100644 --- a/consts/api.ts +++ b/consts/api.ts @@ -1,8 +1,3 @@ -export const ETHPLORER_TOKEN_ENDPOINT = - 'https://api.ethplorer.io/getTokenInfo/'; - -export const HEALTHY_RPC_SERVICES_ARE_OVER = 'Healthy RPC services are over!'; - // TODO: review export const enum API_ROUTES { diff --git a/consts/metrics.ts b/consts/metrics.ts index 353a810d..2b9dfa29 100644 --- a/consts/metrics.ts +++ b/consts/metrics.ts @@ -5,6 +5,5 @@ export const METRICS_PREFIX = 'csm_widget_ui_'; export const enum METRIC_NAMES { REQUESTS_TOTAL = 'requests_total', API_RESPONSE = 'api_response', - SUBGRAPHS_RESPONSE = 'subgraphs_response', ETH_CALL_ADDRESS_TO = 'eth_call_address_to', } diff --git a/consts/tx.ts b/consts/tx.ts deleted file mode 100644 index 246f69f8..00000000 --- a/consts/tx.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { BigNumber } from 'ethers'; - -// TODO: review - -export const WSTETH_APPROVE_GAS_LIMIT = BigNumber.from(78000); - -export const WRAP_FROM_ETH_GAS_LIMIT = BigNumber.from(100000); -export const WRAP_GAS_LIMIT = BigNumber.from(140000); -export const WRAP_GAS_LIMIT_GOERLI = BigNumber.from(120000); -export const UNWRAP_GAS_LIMIT = BigNumber.from(115000); diff --git a/global.d.ts b/global.d.ts index 9f0a2244..3c5f7125 100644 --- a/global.d.ts +++ b/global.d.ts @@ -28,26 +28,15 @@ declare module 'next/config' { defaultChain: string; rpcUrls_1: string | undefined; rpcUrls_17000: string | undefined; - ethplorerApiKey: string | undefined; clApiUrls_1: string | undefined; clApiUrls_17000: string | undefined; - oneInchApiKey: string | undefined; - cspTrustedHosts: string | undefined; cspReportUri: string | undefined; cspReportOnly: string | undefined; - subgraphMainnet: string | undefined; - subgraphGoerli: string | undefined; - subgraphHolesky: string | undefined; - subgraphRequestTimeout: string | undefined; - rateLimit: string; rateLimitTimeFrame: string; - - ethAPIBasePath: string; - rewardsBackendAPI: string | undefined; }; publicRuntimeConfig: { basePath: string | undefined; diff --git a/next.config.mjs b/next.config.mjs index 1a5f7f32..d153d321 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -163,26 +163,15 @@ export default withBundleAnalyzer({ defaultChain: process.env.DEFAULT_CHAIN, rpcUrls_1: process.env.EL_RPC_URLS_1, rpcUrls_17000: process.env.EL_RPC_URLS_17000, - ethplorerApiKey: process.env.ETHPLORER_API_KEY, clApiUrls_1: process.env.CL_API_URLS_1, clApiUrls_17000: process.env.CL_API_URLS_17000, - oneInchApiKey: process.env.ONE_INCH_API_KEY, - cspTrustedHosts: process.env.CSP_TRUSTED_HOSTS, cspReportUri: process.env.CSP_REPORT_URI, cspReportOnly: process.env.CSP_REPORT_ONLY, - subgraphMainnet: process.env.SUBGRAPH_MAINNET, - subgraphGoerli: process.env.SUBGRAPH_GOERLI, - subgraphHolesky: process.env.SUBGRAPH_HOLESKY, - subgraphRequestTimeout: process.env.SUBGRAPH_REQUEST_TIMEOUT, - rateLimit: process.env.RATE_LIMIT, rateLimitTimeFrame: process.env.RATE_LIMIT_TIME_FRAME, - - ethAPIBasePath: process.env.ETH_API_BASE_PATH, - rewardsBackendAPI: process.env.REWARDS_BACKEND, }, // ATTENTION: If you add a new variable you should declare it in `global.d.ts` diff --git a/types/index.ts b/types/index.ts index 2bc24b9c..6a4b3a99 100644 --- a/types/index.ts +++ b/types/index.ts @@ -3,4 +3,3 @@ export * from './common'; export * from './components'; export * from './deposit-data'; export * from './node-operator'; -export * from './subgraph'; diff --git a/types/subgraph.ts b/types/subgraph.ts deleted file mode 100644 index 2aed7a1c..00000000 --- a/types/subgraph.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { CHAINS } from 'consts/chains'; - -export type SubgraphChains = CHAINS; diff --git a/utilsApi/getSubgraphUrl.ts b/utilsApi/getSubgraphUrl.ts deleted file mode 100644 index 56614125..00000000 --- a/utilsApi/getSubgraphUrl.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { secretConfig } from 'config'; -import { CHAINS } from 'consts/chains'; -import { SubgraphChains } from 'types'; - -export const SUBGRAPH_URL = { - [CHAINS.Mainnet]: secretConfig.subgraphMainnet, - [CHAINS.Holesky]: secretConfig.subgraphHolesky, -} as const; - -export const getSubgraphUrl = (chainId: SubgraphChains): string | undefined => { - return SUBGRAPH_URL[chainId]; -}; diff --git a/utilsApi/index.ts b/utilsApi/index.ts index 29a3d3a2..4f48443d 100644 --- a/utilsApi/index.ts +++ b/utilsApi/index.ts @@ -1,6 +1,5 @@ export * from './contractAddressesMetricsMap'; export * from './getEthPrice'; -export * from './getSubgraphUrl'; export * from './nextApiWrappers'; export * from './rpcProviders'; export * from './metrics'; diff --git a/utilsApi/metrics/metrics.ts b/utilsApi/metrics/metrics.ts index 18d00cb5..3c5b2337 100644 --- a/utilsApi/metrics/metrics.ts +++ b/utilsApi/metrics/metrics.ts @@ -6,13 +6,11 @@ import { METRICS_PREFIX } from 'consts/metrics'; import buildInfoJson from 'build-info.json'; import { RequestMetrics } from './request'; -import { SubgraphMetrics } from './subgraph'; class Metrics { registry = new Registry(); // compositions of metric types - subgraph = new SubgraphMetrics(this.registry); request = new RequestMetrics(this.registry); constructor() { diff --git a/utilsApi/metrics/subgraph.ts b/utilsApi/metrics/subgraph.ts deleted file mode 100644 index fef942f4..00000000 --- a/utilsApi/metrics/subgraph.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Histogram, Registry } from 'prom-client'; -import { METRICS_PREFIX, METRIC_NAMES } from 'consts/metrics'; - -export class SubgraphMetrics { - subgraphsResponseTime: Histogram<'subgraphs'>; - - constructor(public registry: Registry) { - this.subgraphsResponseTime = this.subgraphsResponseTimeInit(); - } - - subgraphsResponseTimeInit() { - const subgraphsResponseTimeName = - METRICS_PREFIX + METRIC_NAMES.SUBGRAPHS_RESPONSE; - - return new Histogram({ - name: subgraphsResponseTimeName, - help: 'Subgraphs response time seconds', - buckets: [0.1, 0.2, 0.3, 0.6, 1, 1.5, 2, 5], - registers: [this.registry], - }); - } -} From 80b572781285687acd9806b854289a8e2386e3c2 Mon Sep 17 00:00:00 2001 From: exromany Date: Tue, 18 Mar 2025 07:00:18 +0300 Subject: [PATCH 04/58] feat: hoodi testnet --- .env.example | 6 ++-- config/get-secret-config.ts | 10 +++++- config/rpc/cl.ts | 1 + config/user-config/types.ts | 1 + config/user-config/utils.ts | 1 + consts/chains.ts | 1 + consts/csm-constants.ts | 32 +++++++++++++++---- consts/external-links.ts | 26 ++++++++++++--- consts/hoodi.ts | 22 +++++++++++++ env-dynamics.mjs | 8 +++-- .../required-bond-amount.tsx | 2 +- features/welcome/try-csm/try-csm.tsx | 4 +-- global.d.ts | 4 +++ next.config.mjs | 4 +++ package.json | 4 +-- pages/api/rpc.ts | 1 + providers/web3.tsx | 3 +- shared/hooks/use-external-links.ts | 4 +-- shared/hooks/useCsmContracts.ts | 1 + .../keys/validate/check-network-duplicates.ts | 13 +++----- shared/keys/validate/constants.ts | 8 ++--- shared/keys/validate/validate.ts | 2 +- utilsApi/clApiUrls.ts | 1 + utilsApi/contractAddressesMetricsMap.ts | 4 +-- yarn.lock | 19 +++++++---- 25 files changed, 134 insertions(+), 48 deletions(-) create mode 100644 consts/hoodi.ts diff --git a/.env.example b/.env.example index 7acc7fe6..d14960ca 100644 --- a/.env.example +++ b/.env.example @@ -1,16 +1,16 @@ # RPC API urls by network # 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_17000= +EL_RPC_URLS_560048= # IPFS prefill RPC URLs - list of URLs delimited by commas PREFILL_UNSAFE_EL_RPC_URLS_1= -PREFILL_UNSAFE_EL_RPC_URLS_17000= +PREFILL_UNSAFE_EL_RPC_URLS_560048= # CL API urls by network # CL_API_URLS_{CHAIN_ID} list or URLs delimeted by commas, first entry is primary, else are fallbacks CL_API_URLS_1= -CL_API_URLS_17000= +CL_API_URLS_560048= # MAINTENANCE mode MAINTENANCE= diff --git a/config/get-secret-config.ts b/config/get-secret-config.ts index 84d5e13d..c402182b 100644 --- a/config/get-secret-config.ts +++ b/config/get-secret-config.ts @@ -10,9 +10,11 @@ export type SecretConfigType = Modify< rpcUrls_1: [string, ...string[]]; rpcUrls_17000: [string, ...string[]]; + rpcUrls_560048: [string, ...string[]]; clApiUrls_1: [string, ...string[]]; clApiUrls_17000: [string, ...string[]]; + clApiUrls_560048: [string, ...string[]]; cspReportOnly: boolean; @@ -31,7 +33,7 @@ export const getSecretConfig = (): SecretConfigType => { ...serverRuntimeConfig, // Keep fallback as in 'env-dynamics.mjs' - defaultChain: Number(serverRuntimeConfig.defaultChain) || 17000, + defaultChain: Number(serverRuntimeConfig.defaultChain) || 560048, // Hack: in the current implementation we can treat an empty array as a "tuple" (conditionally) rpcUrls_1: (serverRuntimeConfig.rpcUrls_1?.split(',') ?? []) as [ @@ -42,6 +44,10 @@ export const getSecretConfig = (): SecretConfigType => { string, ...string[], ], + rpcUrls_560048: (serverRuntimeConfig.rpcUrls_560048?.split(',') ?? []) as [ + string, + ...string[], + ], clApiUrls_1: (serverRuntimeConfig.clApiUrls_1?.split(',') ?? []) as [ string, @@ -49,6 +55,8 @@ export const getSecretConfig = (): SecretConfigType => { ], clApiUrls_17000: (serverRuntimeConfig.clApiUrls_17000?.split(',') ?? []) as [string, ...string[]], + clApiUrls_560048: (serverRuntimeConfig.clApiUrls_560048?.split(',') ?? + []) as [string, ...string[]], cspReportOnly: toBoolean(serverRuntimeConfig.cspReportOnly), diff --git a/config/rpc/cl.ts b/config/rpc/cl.ts index 14f0e210..140c4fe9 100644 --- a/config/rpc/cl.ts +++ b/config/rpc/cl.ts @@ -15,6 +15,7 @@ import { config } from '../get-config'; import { useUserConfig } from '../user-config'; export const getBackendApiPath = (chainId: string | number): string => { + if (chainId === CHAINS.Hoodi) return ''; const BASE_URL = typeof window === 'undefined' ? '' : window.location.origin; return `${BASE_URL}/${API_ROUTES.CL}/${chainId}`; }; diff --git a/config/user-config/types.ts b/config/user-config/types.ts index f4c09a4f..1339a6da 100644 --- a/config/user-config/types.ts +++ b/config/user-config/types.ts @@ -6,6 +6,7 @@ export type UserConfigDefaultType = { prefillUnsafeElRpcUrls: { [CHAINS.Mainnet]: string[]; [CHAINS.Holesky]: string[]; + [CHAINS.Hoodi]: string[]; }; walletconnectProjectId: string | undefined; }; diff --git a/config/user-config/utils.ts b/config/user-config/utils.ts index bb8eaeb4..a229f11f 100644 --- a/config/user-config/utils.ts +++ b/config/user-config/utils.ts @@ -15,6 +15,7 @@ export const getUserConfigDefault = (): UserConfigDefaultType => { prefillUnsafeElRpcUrls: { [CHAINS.Mainnet]: config.prefillUnsafeElRpcUrls1, [CHAINS.Holesky]: config.prefillUnsafeElRpcUrls17000, + [CHAINS.Hoodi]: config.prefillUnsafeElRpcUrls560048, }, walletconnectProjectId: config.walletconnectProjectId, }; diff --git a/consts/chains.ts b/consts/chains.ts index 1c5e1012..32aa953e 100644 --- a/consts/chains.ts +++ b/consts/chains.ts @@ -1,4 +1,5 @@ export const enum CHAINS { Mainnet = 1, Holesky = 17000, + Hoodi = 560048, } diff --git a/consts/csm-constants.ts b/consts/csm-constants.ts index e5d9f8d1..172bd9a4 100644 --- a/consts/csm-constants.ts +++ b/consts/csm-constants.ts @@ -1,4 +1,5 @@ -import { CHAINS } from '@lido-sdk/constants'; +import { CHAINS } from 'consts/chains'; +import { CHAINS as ALL_CHAINS } from '@lido-sdk/constants'; import { config } from 'config'; import { HexString } from 'shared/keys'; import { Address } from 'wagmi'; @@ -17,14 +18,14 @@ type CsmContract = type CsmConstants = { contracts: Record; - deploymentBlockNumber: HexString; + deploymentBlockNumber?: HexString; stakingModuleId: number; withdrawalCredentials: Address; retentionPeriodMins: number; slotsPerFrame: number; }; -export const CONSTANTS_BY_NETWORK: Partial> = { +export const CONSTANTS_BY_NETWORK: Record = { [CHAINS.Mainnet]: { contracts: { CSAccounting: '0x4d72BFF1BeaC69925F8Bd12526a39BAAb069e5Da', @@ -42,6 +43,23 @@ export const CONSTANTS_BY_NETWORK: Partial> = { retentionPeriodMins: 80_640, // 8 weeks slotsPerFrame: 32 * 225 * 28, // 28 days }, + [CHAINS.Hoodi]: { + contracts: { + CSAccounting: '0x592FF3c0FEd95909d7770db1659d35B2E1798B21', + CSEarlyAdoption: '0xd9ad1926E1F7bb363E6FA987f720049eDD1F1FA4', + CSFeeDistributor: '0x7D7566db8795015Ff711AD7655e1ED057e8ea155', + CSFeeOracle: '0xCF9230278019830762aC49148Dc9a90981ba157A', + CSModule: '0x5AE927989597213023FfA68D4D3ce109B3959FE4', + CSVerifier: '0x6e51Cb9Ca4D6f918E3d18839ACBe80798068712d', + ExitBusOracle: '0x30308CD8844fb2DB3ec4D056F1d475a802DCA07c', + StakingRouter: '0xCc820558B39ee15C7C45B59390B503b83fb499A8', + }, + deploymentBlockNumber: undefined, + stakingModuleId: 4, + withdrawalCredentials: '0x4473dCDDbf77679A643BdB654dbd86D67F8d32f2', + retentionPeriodMins: 80_640, // 8 weeks + slotsPerFrame: 32 * 225 * 7, // 7 days + }, [CHAINS.Holesky]: { contracts: { CSAccounting: '0xc093e53e8F4b55A223c18A2Da6fA00e60DD5EFE1', @@ -62,9 +80,9 @@ export const CONSTANTS_BY_NETWORK: Partial> = { }; export const getCsmConstants = ( - chainId: CHAINS | undefined = config.defaultChain, + chainId: ALL_CHAINS | undefined = config.defaultChain, ) => { - const constants = CONSTANTS_BY_NETWORK[chainId]; + const constants = CONSTANTS_BY_NETWORK[chainId as unknown as CHAINS]; if (!constants) { throw new Error(`CSM constants for chain [${chainId}] are not specified`); } @@ -72,10 +90,10 @@ export const getCsmConstants = ( }; export const getCsmContractAddress = ( - chainId: CHAINS | undefined, + chainId: ALL_CHAINS | undefined, contract: CsmContract, ): Address => getCsmConstants(chainId).contracts[contract]; export const getCsmContractAddressGetter = - (contract: CsmContract) => (chainId: CHAINS | undefined) => + (contract: CsmContract) => (chainId: ALL_CHAINS) => getCsmContractAddress(chainId, contract); diff --git a/consts/external-links.ts b/consts/external-links.ts index 700bd402..c2315340 100644 --- a/consts/external-links.ts +++ b/consts/external-links.ts @@ -1,4 +1,4 @@ -import { CHAINS } from '@lido-sdk/constants'; +import { CHAINS } from 'consts/chains'; import { config } from 'config'; export const CSM_MAINNET_LINK = 'https://csm.lido.fi/'; @@ -36,9 +36,7 @@ type ExternalLinksConstants = { surveyApi: string; }; -export const EXTERNAL_LINKS_BY_NETWORK: Partial< - Record -> = { +export const EXTERNAL_LINKS_BY_NETWORK: Record = { [CHAINS.Mainnet]: { earlyAdoptionTree: 'https://raw.githubusercontent.com/lidofinance/community-staking-module/v1.0/artifacts/mainnet/early-adoption/merkle-tree.json', @@ -80,6 +78,26 @@ export const EXTERNAL_LINKS_BY_NETWORK: Partial< ratedExplorer: 'https://explorer.rated.network', subscribeEvents: 'https://docs.lido.fi/staking-modules/csm/guides/events', keysApi: 'https://keys-api-holesky.testnet.fi', + surveyApi: '', + }, + // FIXME: links + [CHAINS.Hoodi]: { + earlyAdoptionTree: '', + rewardsTree: '', + earlyAdoptionSources: + 'https://github.com/lidofinance/community-staking-module/blob/v1.0/artifacts/holesky/early-adoption/addresses.json', + earlyAdoptionAbout: + 'https://operatorportal.lido.fi/modules/community-staking-module#block-ef60a1fa96ae4c7995dd7794de2a3e22', + feedbackForm: 'https://forms.gle/ZBUqbykaZokJLf4M7', + stakeWidget: 'https://stake-holesky.testnet.fi', + + feesMonitoring: 'https://fees-monitoring-holesky.testnet.fi', + operatorsWidget: 'https://operators-holesky.testnet.fi', + beaconchain: 'https://holesky.beaconcha.in', + beaconchainDashboard: 'https://v2-beta-holesky.beaconcha.in/dashboard', + ratedExplorer: 'https://explorer.rated.network', + subscribeEvents: 'https://docs.lido.fi/staking-modules/csm/guides/events', + keysApi: 'http://hr6vb81d1ndsx-hoodi-keys-api.valset-01.testnet.fi', surveyApi: 'https://csm-surveys-api-testnet.up.railway.app', }, }; diff --git a/consts/hoodi.ts b/consts/hoodi.ts new file mode 100644 index 00000000..a6a4bbbf --- /dev/null +++ b/consts/hoodi.ts @@ -0,0 +1,22 @@ +import { Chain } from 'wagmi'; + +export const hoodi: Readonly = { + id: 560048, + network: 'hoodi', + name: 'Hoodi', + nativeCurrency: { + name: 'Hoodie Ether', + symbol: 'ETH', + decimals: 18, + }, + rpcUrls: { + default: { + http: ['https://rpc.hoodi.ethpandaops.io'], + }, + public: { + http: ['https://rpc.hoodi.ethpandaops.io'], + }, + }, + contracts: {}, + testnet: true, +}; diff --git a/env-dynamics.mjs b/env-dynamics.mjs index 0061b6e1..a40c198d 100644 --- a/env-dynamics.mjs +++ b/env-dynamics.mjs @@ -18,12 +18,12 @@ const toBoolean = (dataStr) => { /** @type string */ export const matomoHost = process.env.MATOMO_URL; /** @type number */ -export const defaultChain = parseInt(process.env.DEFAULT_CHAIN, 10) || 17000; +export const defaultChain = parseInt(process.env.DEFAULT_CHAIN, 10) || 560048; /** @type number[] */ export const supportedChains = process.env?.SUPPORTED_CHAINS?.split(',').map( (chainId) => parseInt(chainId, 10), -) ?? [17000]; +) ?? [560048]; /** @type string */ export const walletconnectProjectId = process.env.WALLETCONNECT_PROJECT_ID; @@ -38,6 +38,10 @@ export const prefillUnsafeElRpcUrls1 = export const prefillUnsafeElRpcUrls17000 = process.env.PREFILL_UNSAFE_EL_RPC_URLS_17000?.split(',') ?? []; +/** @type string[] */ +export const prefillUnsafeElRpcUrls560048 = + process.env.PREFILL_UNSAFE_EL_RPC_URLS_560048?.split(',') ?? []; + /** @type string */ export const widgetApiBasePathForIpfs = process.env.WIDGET_API_BASE_PATH_FOR_IPFS; diff --git a/features/starter-pack/stacter-pack-section/required-bond-amount.tsx b/features/starter-pack/stacter-pack-section/required-bond-amount.tsx index 85b897e7..068eaea5 100644 --- a/features/starter-pack/stacter-pack-section/required-bond-amount.tsx +++ b/features/starter-pack/stacter-pack-section/required-bond-amount.tsx @@ -26,7 +26,7 @@ export const RequiredBondAmount: FC = () => { ) : ( )} diff --git a/features/welcome/try-csm/try-csm.tsx b/features/welcome/try-csm/try-csm.tsx index a092b7b1..17ffce5c 100644 --- a/features/welcome/try-csm/try-csm.tsx +++ b/features/welcome/try-csm/try-csm.tsx @@ -17,7 +17,7 @@ export const TryCSM: FC = () => { - Try CSM on Holesky + Try CSM on Hoodi { - CSM uses Holesky as a testnet playground for those who want to try the + CSM uses Hoodi as a testnet playground for those who want to try the module in action in a test environment. diff --git a/global.d.ts b/global.d.ts index 3c5f7125..b23d0dd4 100644 --- a/global.d.ts +++ b/global.d.ts @@ -26,10 +26,14 @@ declare module 'next/config' { maintenance: boolean; defaultChain: string; + rpcUrls_1: string | undefined; rpcUrls_17000: string | undefined; + rpcUrls_560048: string | undefined; + clApiUrls_1: string | undefined; clApiUrls_17000: string | undefined; + clApiUrls_560048: string | undefined; cspTrustedHosts: string | undefined; cspReportUri: string | undefined; diff --git a/next.config.mjs b/next.config.mjs index d153d321..b6f98bcc 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -161,10 +161,14 @@ export default withBundleAnalyzer({ maintenance, defaultChain: process.env.DEFAULT_CHAIN, + rpcUrls_1: process.env.EL_RPC_URLS_1, rpcUrls_17000: process.env.EL_RPC_URLS_17000, + rpcUrls_560048: process.env.EL_RPC_URLS_560048, + clApiUrls_1: process.env.CL_API_URLS_1, clApiUrls_17000: process.env.CL_API_URLS_17000, + clApiUrls_560048: process.env.CL_API_URLS_560048, cspTrustedHosts: process.env.CSP_TRUSTED_HOSTS, cspReportUri: process.env.CSP_REPORT_URI, diff --git a/package.json b/package.json index 95877de4..964a0e63 100644 --- a/package.json +++ b/package.json @@ -25,9 +25,9 @@ "@ethersproject/contracts": "^5.7.0", "@ethersproject/providers": "^5.7.0", "@ethersproject/units": "^5.7.0", - "@lido-sdk/constants": "^3.2.1", + "@lido-sdk/constants": "^3.5.0", "@lido-sdk/contracts": "^3.0.4", - "@lido-sdk/fetch": "^2.1.12", + "@lido-sdk/fetch": "^2.3.1", "@lido-sdk/helpers": "^1.5.1", "@lido-sdk/providers": "^1.4.14", "@lido-sdk/react": "^2.0.5", diff --git a/pages/api/rpc.ts b/pages/api/rpc.ts index a7a4f6ec..6ff99037 100644 --- a/pages/api/rpc.ts +++ b/pages/api/rpc.ts @@ -31,6 +31,7 @@ const rpc = rpcFactory({ providers: { [CHAINS.Mainnet]: secretConfig.rpcUrls_1, [CHAINS.Holesky]: secretConfig.rpcUrls_17000, + [CHAINS.Hoodi]: secretConfig.rpcUrls_560048, }, validation: { allowedRPCMethods: [ diff --git a/providers/web3.tsx b/providers/web3.tsx index 9732628a..95079ebf 100644 --- a/providers/web3.tsx +++ b/providers/web3.tsx @@ -4,6 +4,7 @@ import { WagmiConfig, createClient, configureChains, Chain } from 'wagmi'; import * as wagmiChains from 'wagmi/chains'; import { getStaticRpcBatchProvider } from '@lido-sdk/providers'; +import { hoodi } from 'consts/hoodi'; import { useUserConfig } from 'config/user-config'; import { useGetRpcUrlByChainId } from 'config/rpc'; import { CHAINS } from 'consts/chains'; @@ -11,7 +12,7 @@ import { ConnectWalletModal } from 'shared/wallet/connect-wallet-modal'; import { SDKLegacyProvider } from './sdk-legacy'; -const wagmiChainsArray = Object.values({ ...wagmiChains, holesky }); +const wagmiChainsArray = Object.values({ ...wagmiChains, hoodi, holesky }); const Web3Provider: FC = ({ children }) => { const { diff --git a/shared/hooks/use-external-links.ts b/shared/hooks/use-external-links.ts index ad8f572c..4d129f74 100644 --- a/shared/hooks/use-external-links.ts +++ b/shared/hooks/use-external-links.ts @@ -38,13 +38,13 @@ export const useOperatorPortalLink = () => { export const useRatedLink = () => { const nodeOperatorId = useNodeOperatorId(); - const network = defaultChain === CHAINS.Mainnet ? 'mainnet' : 'holesky'; + const network = defaultChain === CHAINS.Mainnet ? 'mainnet' : 'hoodi'; return `${links.ratedExplorer}/o/CSM%20Operator%20${nodeOperatorId}%20-%20Lido%20Community%20Staking%20Module?network=${network}`; }; export const useEthSeerLink = () => { const nodeOperatorId = useNodeOperatorId(); - const network = defaultChain === CHAINS.Mainnet ? 'mainnet' : 'holesky'; + const network = defaultChain === CHAINS.Mainnet ? 'mainnet' : 'hoodi'; if (links.ethseerDashboard) { return `${links.ethseerDashboard}/entity/csm_operator${nodeOperatorId}_lido?network=${network}`; } diff --git a/shared/hooks/useCsmContracts.ts b/shared/hooks/useCsmContracts.ts index b06d9ba0..829d1664 100644 --- a/shared/hooks/useCsmContracts.ts +++ b/shared/hooks/useCsmContracts.ts @@ -19,6 +19,7 @@ const CSModule = contractHooksFactory( export const useCSModuleRPC = CSModule.useContractRPC; export const useCSModuleWeb3 = CSModule.useContractWeb3; +// TODO: drop after removing Holesky const CSModuleOld = contractHooksFactory( CSModuleOld__factory, getCsmContractAddressGetter('CSModule'), diff --git a/shared/keys/validate/check-network-duplicates.ts b/shared/keys/validate/check-network-duplicates.ts index ccb9c59e..0981b4bc 100644 --- a/shared/keys/validate/check-network-duplicates.ts +++ b/shared/keys/validate/check-network-duplicates.ts @@ -18,12 +18,12 @@ type ResponseData = { meta: any; }; -const findDuplicate = async (pubkeys: HexString[], chainId: CHAINS) => { +const findDuplicate = async (pubkeys: HexString[]) => { try { // TODO: timeout // TODO: cache - const url = getExternalLinks(chainId).keysApi; - const response = await fetch(`${url}/v1/keys/find`, { + const { keysApi } = getExternalLinks(); + const response = await fetch(`${keysApi}/v1/keys/find`, { method: 'post', body: JSON.stringify({ pubkeys }), headers: { 'Content-Type': 'application/json' }, @@ -41,14 +41,11 @@ const toHexString = (data: string): HexString => { return `0x${data}`; }; -export const checkNetworkDuplicates = async ( - depositData: DepositData[], - chainId: CHAINS, -) => { +export const checkNetworkDuplicates = async (depositData: DepositData[]) => { const pubkeys = depositData.map((data) => toHexString(data.pubkey.toLowerCase()), ); - const duplicateKey = await findDuplicate(pubkeys, chainId); + const duplicateKey = await findDuplicate(pubkeys); if (duplicateKey) { throw new Error( diff --git a/shared/keys/validate/constants.ts b/shared/keys/validate/constants.ts index 2e3e0869..66f36030 100644 --- a/shared/keys/validate/constants.ts +++ b/shared/keys/validate/constants.ts @@ -12,19 +12,15 @@ export const FIXED_WC_PREFIX = '010000000000000000000000'; export const FIXED_NETWORK: { [key in CHAINS]?: string[]; } = { - [CHAINS.Goerli]: ['goerli', 'prater'], [CHAINS.Mainnet]: ['mainnet'], - [CHAINS.Ropsten]: ['mainnet'], - [CHAINS.Kiln]: ['kiln'], [CHAINS.Holesky]: ['holesky'], + [CHAINS.Hoodi]: ['hoodi'], }; export const FIXED_FORK_VERSION: { [key in CHAINS]?: string; } = { - [CHAINS.Goerli]: '00001020', [CHAINS.Mainnet]: '00000000', [CHAINS.Holesky]: '01017000', - [CHAINS.Ropsten]: '00000000', - [CHAINS.Kiln]: '70000069', + [CHAINS.Hoodi]: '10000910', }; diff --git a/shared/keys/validate/validate.ts b/shared/keys/validate/validate.ts index 39f71192..c9ebd372 100644 --- a/shared/keys/validate/validate.ts +++ b/shared/keys/validate/validate.ts @@ -18,7 +18,7 @@ export const validate = async ( checkLength(depositData, keysUploadLimit); checkDuplicates(depositData); checkPreviouslySubmittedDuplicates(depositData, chainId, blockNumber); - await checkNetworkDuplicates(depositData, chainId); + await checkNetworkDuplicates(depositData); return null; } catch (error) { diff --git a/utilsApi/clApiUrls.ts b/utilsApi/clApiUrls.ts index ffb12a52..ce31525b 100644 --- a/utilsApi/clApiUrls.ts +++ b/utilsApi/clApiUrls.ts @@ -4,4 +4,5 @@ import { CHAINS } from 'consts/chains'; export const clApiUrls: Record = { [CHAINS.Mainnet]: secretConfig.clApiUrls_1, [CHAINS.Holesky]: secretConfig.clApiUrls_17000, + [CHAINS.Hoodi]: secretConfig.clApiUrls_560048, }; diff --git a/utilsApi/contractAddressesMetricsMap.ts b/utilsApi/contractAddressesMetricsMap.ts index bde9295f..4f2420f3 100644 --- a/utilsApi/contractAddressesMetricsMap.ts +++ b/utilsApi/contractAddressesMetricsMap.ts @@ -136,7 +136,7 @@ const aggregatorMainnetAddress = METRIC_CONTRACT_ADDRESS_GETTERS[ ](CHAINS.Mainnet) as HexString; const prefilledAddresses = - config.defaultChain === CHAINS.Holesky && + config.defaultChain === CHAINS.Hoodi && !config.supportedChains.includes(CHAINS.Mainnet) ? ({ [CHAINS.Mainnet]: [aggregatorMainnetAddress], @@ -146,7 +146,7 @@ const prefilledAddresses = const prefilledMetricAddresses: Partial< Record> > = - config.defaultChain === CHAINS.Holesky && + config.defaultChain === CHAINS.Hoodi && !config.supportedChains.includes(CHAINS.Mainnet) ? { [CHAINS.Mainnet]: { diff --git a/yarn.lock b/yarn.lock index 5fc0ddbc..14b66e1c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2545,13 +2545,20 @@ bignumber.js "^9.1.2" rxjs "^7.8.1" -"@lido-sdk/constants@3.3.0", "@lido-sdk/constants@^3.2.1": +"@lido-sdk/constants@3.3.0": version "3.3.0" resolved "https://registry.yarnpkg.com/@lido-sdk/constants/-/constants-3.3.0.tgz#044b652a000a067b9ee0ae9f58814434779bb108" integrity sha512-R5XINgj/EQvyBfPF+Zv9B/ycFCqARD8rbWDp3J4luAMDbSUP3ncGw0x7Aj836k96eVnrh+jfd2tz/c99Dm6IaA== dependencies: tiny-invariant "^1.1.0" +"@lido-sdk/constants@3.5.0", "@lido-sdk/constants@^3.5.0": + version "3.5.0" + resolved "https://registry.yarnpkg.com/@lido-sdk/constants/-/constants-3.5.0.tgz#74d7e21a36cba3abce83cc63db0e50a03b92492e" + integrity sha512-Br5md5TP5g2uka3wTtpoUy92wYzTdA+y8U8p+EbZsnm/aMQyrvu0gA7b0pC4l/xODNEEIAGwThCGDb1sP9W39A== + dependencies: + tiny-invariant "^1.1.0" + "@lido-sdk/contracts@3.0.5", "@lido-sdk/contracts@^3.0.4": version "3.0.5" resolved "https://registry.yarnpkg.com/@lido-sdk/contracts/-/contracts-3.0.5.tgz#9778e83258de241e26bcd5c76589ff010856afbb" @@ -2560,12 +2567,12 @@ "@lido-sdk/constants" "3.3.0" tiny-invariant "^1.1.0" -"@lido-sdk/fetch@^2.1.12": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@lido-sdk/fetch/-/fetch-2.2.0.tgz#e0034083edaed471f29163ebfdccc9b10cd58ea7" - integrity sha512-Jwi2W5azP4uuhmsWexkk4al0Y+pMx5PpB74klIBzgX/eoVnK+NOTxLemJa6GDq4KTIlriAM79jscY0TJvJJWQA== +"@lido-sdk/fetch@^2.3.1": + version "2.3.1" + resolved "https://registry.yarnpkg.com/@lido-sdk/fetch/-/fetch-2.3.1.tgz#f9fb3359dc76a8922083b3ec91a7112939a4b266" + integrity sha512-ylfyqo1EyB5oykUMBNUx1mumVG2trx/ZrWNOIPBDJVSuDs0GBk9cFPil9dRj8QJh/gQyQdIPUsjv+HlwtdJqjQ== dependencies: - "@lido-sdk/constants" "3.3.0" + "@lido-sdk/constants" "3.5.0" node-fetch "^2.6.7" tiny-invariant "^1.1.0" From 6f32b8bd90ce73502c41f16423869f0a27686326 Mon Sep 17 00:00:00 2001 From: exromany Date: Tue, 18 Mar 2025 12:28:54 +0300 Subject: [PATCH 05/58] fix: csm addresses --- consts/csm-constants.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/consts/csm-constants.ts b/consts/csm-constants.ts index 172bd9a4..0fe6f66b 100644 --- a/consts/csm-constants.ts +++ b/consts/csm-constants.ts @@ -45,12 +45,12 @@ export const CONSTANTS_BY_NETWORK: Record = { }, [CHAINS.Hoodi]: { contracts: { - CSAccounting: '0x592FF3c0FEd95909d7770db1659d35B2E1798B21', - CSEarlyAdoption: '0xd9ad1926E1F7bb363E6FA987f720049eDD1F1FA4', - CSFeeDistributor: '0x7D7566db8795015Ff711AD7655e1ED057e8ea155', - CSFeeOracle: '0xCF9230278019830762aC49148Dc9a90981ba157A', - CSModule: '0x5AE927989597213023FfA68D4D3ce109B3959FE4', - CSVerifier: '0x6e51Cb9Ca4D6f918E3d18839ACBe80798068712d', + CSAccounting: '0xA54b90BA34C5f326BC1485054080994e38FB4C60', + CSEarlyAdoption: '0x3281b9E45518F462E594697f8fba1896a8B43939', + CSFeeDistributor: '0xaCd9820b0A2229a82dc1A0770307ce5522FF3582', + CSFeeOracle: '0xe7314f561B2e72f9543F1004e741bab6Fc51028B', + CSModule: '0x79CEf36D84743222f37765204Bec41E92a93E59d', + CSVerifier: '0x16D0f6068D211608e3703323314aa976a6492D09', ExitBusOracle: '0x30308CD8844fb2DB3ec4D056F1d475a802DCA07c', StakingRouter: '0xCc820558B39ee15C7C45B59390B503b83fb499A8', }, From 22e0021622c46edcc69bed37230c1cacc0dac9df Mon Sep 17 00:00:00 2001 From: exromany Date: Tue, 18 Mar 2025 13:08:00 +0300 Subject: [PATCH 06/58] chore: vendor @lido-sdk/providers & @lido-sdk/react chore: drop vendor chore: drop vendor --- .../use-add-bond-form-network-data.tsx | 8 +-- .../use-add-keys-form-network-data.tsx | 8 +-- .../use-submit-keys-form-network-data.tsx | 8 +-- package.json | 8 +-- shared/hooks/index.ts | 1 + shared/hooks/use-lido-contracts.ts | 23 +++++++ shared/hooks/use-permit-signature.ts | 12 ++-- shared/hooks/useNodeOperatorRewards.ts | 11 +-- shared/hooks/useStakingLimitInfo.ts | 4 +- shared/hooks/useStethByWsteth.ts | 3 +- shared/hooks/useWstethBySteth.ts | 3 +- yarn.lock | 68 ++++++++++++------- 12 files changed, 98 insertions(+), 59 deletions(-) create mode 100644 shared/hooks/use-lido-contracts.ts diff --git a/features/add-bond/add-bond-form/context/use-add-bond-form-network-data.tsx b/features/add-bond/add-bond-form/context/use-add-bond-form-network-data.tsx index 3326d00e..54aedd5e 100644 --- a/features/add-bond/add-bond-form/context/use-add-bond-form-network-data.tsx +++ b/features/add-bond/add-bond-form/context/use-add-bond-form-network-data.tsx @@ -1,8 +1,4 @@ -import { - useEthereumBalance, - useSTETHBalance, - useWSTETHBalance, -} from '@lido-sdk/react'; +import { useEthereumBalance } from '@lido-sdk/react'; import { STRATEGY_LAZY } from 'consts/swr-strategies'; import { useNodeOperatorId } from 'providers/node-operator-provider'; import { useCallback, useMemo } from 'react'; @@ -10,6 +6,8 @@ import { useCsmPaused, useNodeOperatorBalance, useStakingLimitInfo, + useSTETHBalance, + useWSTETHBalance, } from 'shared/hooks'; import { type AddBondFormNetworkData } from '../context/types'; diff --git a/features/add-keys/add-keys/context/use-add-keys-form-network-data.tsx b/features/add-keys/add-keys/context/use-add-keys-form-network-data.tsx index 1bb8aeb2..6b8e2b79 100644 --- a/features/add-keys/add-keys/context/use-add-keys-form-network-data.tsx +++ b/features/add-keys/add-keys/context/use-add-keys-form-network-data.tsx @@ -1,8 +1,4 @@ -import { - useEthereumBalance, - useSTETHBalance, - useWSTETHBalance, -} from '@lido-sdk/react'; +import { useEthereumBalance } from '@lido-sdk/react'; import { STRATEGY_LAZY } from 'consts/swr-strategies'; import { useNodeOperatorId } from 'providers/node-operator-provider'; import { useCallback, useMemo } from 'react'; @@ -15,6 +11,8 @@ import { useNodeOperatorCurveId, useNonWithdrawnKeysCount, useStakingLimitInfo, + useSTETHBalance, + useWSTETHBalance, } from 'shared/hooks'; import { useBlockNumber } from 'wagmi'; import { type AddKeysFormNetworkData } from './types'; diff --git a/features/create-node-operator/submit-keys-form/context/use-submit-keys-form-network-data.tsx b/features/create-node-operator/submit-keys-form/context/use-submit-keys-form-network-data.tsx index 3aba60b8..16e70653 100644 --- a/features/create-node-operator/submit-keys-form/context/use-submit-keys-form-network-data.tsx +++ b/features/create-node-operator/submit-keys-form/context/use-submit-keys-form-network-data.tsx @@ -1,8 +1,4 @@ -import { - useEthereumBalance, - useSTETHBalance, - useWSTETHBalance, -} from '@lido-sdk/react'; +import { useEthereumBalance } from '@lido-sdk/react'; import { STRATEGY_LAZY } from 'consts/swr-strategies'; import { useCallback, useMemo } from 'react'; import { @@ -14,6 +10,8 @@ import { useKeysAvailable, useKeysUploadLimit, useStakingLimitInfo, + useSTETHBalance, + useWSTETHBalance, } from 'shared/hooks'; import { useBlockNumber } from 'wagmi'; import { type SubmitKeysFormNetworkData } from './types'; diff --git a/package.json b/package.json index 964a0e63..b8a57909 100644 --- a/package.json +++ b/package.json @@ -26,11 +26,11 @@ "@ethersproject/providers": "^5.7.0", "@ethersproject/units": "^5.7.0", "@lido-sdk/constants": "^3.5.0", - "@lido-sdk/contracts": "^3.0.4", + "@lido-sdk/contracts": "^3.1.2", "@lido-sdk/fetch": "^2.3.1", - "@lido-sdk/helpers": "^1.5.1", - "@lido-sdk/providers": "^1.4.14", - "@lido-sdk/react": "^2.0.5", + "@lido-sdk/helpers": "^1.7.0", + "@lido-sdk/providers": "1.4.14", + "@lido-sdk/react": "2.0.5", "@lidofinance/address": "^1.4.0", "@lidofinance/analytics-matomo": "^0.51.0", "@lidofinance/api-logger": "^0.47.0", diff --git a/shared/hooks/index.ts b/shared/hooks/index.ts index e0072a6b..b94a509a 100644 --- a/shared/hooks/index.ts +++ b/shared/hooks/index.ts @@ -22,6 +22,7 @@ export * from './use-keys-available'; export * from './use-keys-cl-status'; export * from './use-keys-upload-limit'; export * from './use-keys-with-status'; +export * from './use-lido-contracts'; export * from './use-mainnet-static-rpc-provider'; export * from './use-network-duplicates'; export * from './use-node-operators-fetcher-from-events'; diff --git a/shared/hooks/use-lido-contracts.ts b/shared/hooks/use-lido-contracts.ts new file mode 100644 index 00000000..67df7019 --- /dev/null +++ b/shared/hooks/use-lido-contracts.ts @@ -0,0 +1,23 @@ +import { getTokenAddress, TOKENS } from '@lido-sdk/constants'; +import { StethAbiFactory, WstethAbiFactory } from '@lido-sdk/contracts'; +import { contractHooksFactory, hooksFactory } from '@lido-sdk/react'; + +const stethContract = contractHooksFactory(StethAbiFactory, (chainId) => + getTokenAddress(chainId, TOKENS.STETH), +); +export const useSTETHContractRPC = stethContract.useContractRPC; + +const stethMethods = hooksFactory((chainId) => + getTokenAddress(chainId, TOKENS.STETH), +); +export const useSTETHBalance = stethMethods.useTokenBalance; + +const wstethContract = contractHooksFactory(WstethAbiFactory, (chainId) => + getTokenAddress(chainId, TOKENS.WSTETH), +); +export const useWSTETHContractRPC = wstethContract.useContractRPC; + +const wstehMethods = hooksFactory((chainId) => + getTokenAddress(chainId, TOKENS.WSTETH), +); +export const useWSTETHBalance = wstehMethods.useTokenBalance; diff --git a/shared/hooks/use-permit-signature.ts b/shared/hooks/use-permit-signature.ts index 2888556f..a978779b 100644 --- a/shared/hooks/use-permit-signature.ts +++ b/shared/hooks/use-permit-signature.ts @@ -1,15 +1,15 @@ import { hexValue, splitSignature } from '@ethersproject/bytes'; import { StethAbi } from '@lido-sdk/contracts'; -import { - useSDK, - useSTETHContractRPC, - useWSTETHContractRPC, -} from '@lido-sdk/react'; +import { useSDK } from '@lido-sdk/react'; import { TOKENS } from 'consts/tokens'; import { getUnixTime, hoursToSeconds } from 'date-fns/fp'; import { BigNumber, BytesLike, TypedDataDomain } from 'ethers'; import { useCallback } from 'react'; -import { useAccount } from 'shared/hooks'; +import { + useAccount, + useSTETHContractRPC, + useWSTETHContractRPC, +} from 'shared/hooks'; import invariant from 'tiny-invariant'; import { Address, useChainId } from 'wagmi'; diff --git a/shared/hooks/useNodeOperatorRewards.ts b/shared/hooks/useNodeOperatorRewards.ts index 2db111b1..acb38bad 100644 --- a/shared/hooks/useNodeOperatorRewards.ts +++ b/shared/hooks/useNodeOperatorRewards.ts @@ -1,14 +1,17 @@ import { Zero } from '@ethersproject/constants'; -import { useContractSWR, useSTETHContractRPC } from '@lido-sdk/react'; +import { useContractSWR } from '@lido-sdk/react'; import { StandardMerkleTree } from '@openzeppelin/merkle-tree'; import { STRATEGY_LAZY } from 'consts/swr-strategies'; import { BigNumber } from 'ethers'; import { useMemo } from 'react'; import { NodeOperatorId, RewardProof, RewardsBalance } from 'types'; import { findIndexAndLeaf } from 'utils'; -import { useCSFeeDistributorRPC } from './useCsmContracts'; -import { useFeeDistributorTree } from './useFeeDistributorTree'; -import { useMergeSwr } from './useMergeSwr'; +import { + useCSFeeDistributorRPC, + useFeeDistributorTree, + useMergeSwr, + useSTETHContractRPC, +} from 'shared/hooks'; export type RewardsTreeLeaf = [NodeOperatorId, string]; diff --git a/shared/hooks/useStakingLimitInfo.ts b/shared/hooks/useStakingLimitInfo.ts index 16634b7e..6542a136 100644 --- a/shared/hooks/useStakingLimitInfo.ts +++ b/shared/hooks/useStakingLimitInfo.ts @@ -1,9 +1,9 @@ -import { useLidoSWR, useSTETHContractRPC } from '@lido-sdk/react'; +import { useLidoSWR } from '@lido-sdk/react'; import { BigNumber } from 'ethers'; import { Zero } from '@ethersproject/constants'; import { STRATEGY_LAZY } from 'consts/swr-strategies'; -import { useAccount } from './use-account'; +import { useAccount, useSTETHContractRPC } from 'shared/hooks'; const getMaxStakeAmount = (limitInfo: { isStakingPaused: boolean; diff --git a/shared/hooks/useStethByWsteth.ts b/shared/hooks/useStethByWsteth.ts index c064f7a3..1fe056d7 100644 --- a/shared/hooks/useStethByWsteth.ts +++ b/shared/hooks/useStethByWsteth.ts @@ -1,6 +1,7 @@ -import { useContractSWR, useWSTETHContractRPC } from '@lido-sdk/react'; +import { useContractSWR } from '@lido-sdk/react'; import { STRATEGY_LAZY } from 'consts/swr-strategies'; import { BigNumber } from 'ethers'; +import { useWSTETHContractRPC } from 'shared/hooks'; export const useStethByWsteth = (wsteth: BigNumber | undefined) => { return useContractSWR({ diff --git a/shared/hooks/useWstethBySteth.ts b/shared/hooks/useWstethBySteth.ts index fce2953f..83cea06d 100644 --- a/shared/hooks/useWstethBySteth.ts +++ b/shared/hooks/useWstethBySteth.ts @@ -1,6 +1,7 @@ -import { useContractSWR, useWSTETHContractRPC } from '@lido-sdk/react'; +import { useContractSWR } from '@lido-sdk/react'; import { STRATEGY_LAZY } from 'consts/swr-strategies'; import { BigNumber } from 'ethers'; +import { useWSTETHContractRPC } from 'shared/hooks'; export const useWstethBySteth = (steth: BigNumber | undefined) => { return useContractSWR({ diff --git a/yarn.lock b/yarn.lock index 14b66e1c..a4187705 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2545,10 +2545,10 @@ bignumber.js "^9.1.2" rxjs "^7.8.1" -"@lido-sdk/constants@3.3.0": - version "3.3.0" - resolved "https://registry.yarnpkg.com/@lido-sdk/constants/-/constants-3.3.0.tgz#044b652a000a067b9ee0ae9f58814434779bb108" - integrity sha512-R5XINgj/EQvyBfPF+Zv9B/ycFCqARD8rbWDp3J4luAMDbSUP3ncGw0x7Aj836k96eVnrh+jfd2tz/c99Dm6IaA== +"@lido-sdk/constants@3.2.1": + version "3.2.1" + resolved "https://registry.yarnpkg.com/@lido-sdk/constants/-/constants-3.2.1.tgz#0c4582d7e76e4f8bc42e8f3c0d14dc0fbe481d77" + integrity sha512-zes0Mw0r1nEQYBNHV5fxK2H9Byowejy4haFy9LYDh1nL72aNJzzdh5S5iM+pKlEuLHQJHV5lVO/k9tunNJIKqQ== dependencies: tiny-invariant "^1.1.0" @@ -2559,12 +2559,20 @@ dependencies: tiny-invariant "^1.1.0" -"@lido-sdk/contracts@3.0.5", "@lido-sdk/contracts@^3.0.4": - version "3.0.5" - resolved "https://registry.yarnpkg.com/@lido-sdk/contracts/-/contracts-3.0.5.tgz#9778e83258de241e26bcd5c76589ff010856afbb" - integrity sha512-piu5wKqxs9QS85qG9OrwXfdg5ZCg+xlggj6Q8PMo9qbAk0UzBVuU7CcXUalpKeJpTQMV4SnvcJ0dwZNR2weK8g== +"@lido-sdk/contracts@3.0.4": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@lido-sdk/contracts/-/contracts-3.0.4.tgz#85e3b203aa0a38841ecf22d7ac4e5f8d70848920" + integrity sha512-oW7gyHKcrss77sEPMmYm38M0CQ5+3GGlNewu9D+UJhtxRpLa+Jh3nWEd5tq/hMdMSN9cGoerVKFfBAhw6zKajg== dependencies: - "@lido-sdk/constants" "3.3.0" + "@lido-sdk/constants" "3.2.1" + tiny-invariant "^1.1.0" + +"@lido-sdk/contracts@^3.1.2": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@lido-sdk/contracts/-/contracts-3.1.2.tgz#b534cc536dd8c1bb79854132cd0b14207a7afc22" + integrity sha512-a7sYlegPQ24V6tX1uCneURInnkbRj4QOlkFfzAlc3Fy/+4/JCvt6mq84FD1I0pq6bOaKjDu6nwP9RVr22z4Ojw== + dependencies: + "@lido-sdk/constants" "3.5.0" tiny-invariant "^1.1.0" "@lido-sdk/fetch@^2.3.1": @@ -2576,29 +2584,37 @@ node-fetch "^2.6.7" tiny-invariant "^1.1.0" -"@lido-sdk/helpers@1.6.0", "@lido-sdk/helpers@^1.5.1": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@lido-sdk/helpers/-/helpers-1.6.0.tgz#551cde8aa1251b310d4e1f93f7ec12680992c639" - integrity sha512-rg8sV7l3SWebx8tiagaDf+Q1F+UfgZ2FS31NDPzBUtG++QKP+9V1hTOrpwLNsmJhqQ0zbNhm84+ykHbCmEO+Cw== +"@lido-sdk/helpers@1.5.1": + version "1.5.1" + resolved "https://registry.yarnpkg.com/@lido-sdk/helpers/-/helpers-1.5.1.tgz#ced13f1df6e34a1d4ad551fde299524dc237b694" + integrity sha512-n8sTliverpxOy7PeTCUyG+bQPIJdg57AOON+6X2tZ19JxU3r6ZhHzo33x/9022aKu0A/Ya7edREDB6MadymdRg== dependencies: - "@lido-sdk/constants" "3.3.0" + "@lido-sdk/constants" "3.2.1" tiny-invariant "^1.1.0" -"@lido-sdk/providers@^1.4.14": - version "1.4.15" - resolved "https://registry.yarnpkg.com/@lido-sdk/providers/-/providers-1.4.15.tgz#2640afbd247cff90952d6710888d8b6a3c2ad0cd" - integrity sha512-155BlcEgKWFlC17itMAeflhfRnF+ejrRUOONfexgIzjcOtam+PB1WNLzpXj8zkhcLbuMW11zYJ0cfIc4QsToag== +"@lido-sdk/helpers@^1.7.0": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@lido-sdk/helpers/-/helpers-1.7.0.tgz#ba1d75d9c394bcc27be05eda9755a7d968809b6f" + integrity sha512-adniJaFUHbw5cDxK+cVpuS85/lahYWqaQZo/vpM1dyMgfaYI1SdK7aLKXSMqxJxp6CI5Bx0uq0KS6ceQYqS47g== dependencies: - "@lido-sdk/constants" "3.3.0" + "@lido-sdk/constants" "3.5.0" + tiny-invariant "^1.1.0" -"@lido-sdk/react@^2.0.5": - version "2.0.6" - resolved "https://registry.yarnpkg.com/@lido-sdk/react/-/react-2.0.6.tgz#f1a245803eb7136f396ceff695a598b0c92056dd" - integrity sha512-4Gwnl+6v/3JS6yyfbyyJj4P/r6slcfW8GESSl5XvPQcK3T/Jt54MN3vIv1tmFyPr1RM/vxtiR4doR+jKEItadA== +"@lido-sdk/providers@1.4.14": + version "1.4.14" + resolved "https://registry.yarnpkg.com/@lido-sdk/providers/-/providers-1.4.14.tgz#b7c714aa753d662c0d51f71ee4990b3cb78ce790" + integrity sha512-m422uXuaGoXoUlF8oyFTIQsj8ljVet/x7nK0xF8UoURm/iuaAhTbEXpcxhmkx8JSSDli1928apJRAwxG0McgnQ== + dependencies: + "@lido-sdk/constants" "3.2.1" + +"@lido-sdk/react@2.0.5": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@lido-sdk/react/-/react-2.0.5.tgz#13496354863bcd98f78cf223ac65254e9620419a" + integrity sha512-XRrO1Zg13IJO0TKhqT/TBJYTWuZg+2+8T+FPmegxwe7mfJSrg025lhtRPW8cjLHvPvVsW6RVgV/vW9iIeHvYpA== dependencies: - "@lido-sdk/constants" "3.3.0" - "@lido-sdk/contracts" "3.0.5" - "@lido-sdk/helpers" "1.6.0" + "@lido-sdk/constants" "3.2.1" + "@lido-sdk/contracts" "3.0.4" + "@lido-sdk/helpers" "1.5.1" swr "^1.0.1" tiny-invariant "^1.1.0" tiny-warning "^1.0.3" From 3ceb5289750ca1e65ed90b40e215fc60cf151620 Mon Sep 17 00:00:00 2001 From: exromany Date: Tue, 18 Mar 2025 13:11:28 +0300 Subject: [PATCH 07/58] fix: deposit queue with dividing by zero --- .../deposit-queue/use-deposit-queue-graph.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/features/view-keys/deposit-queue/use-deposit-queue-graph.ts b/features/view-keys/deposit-queue/use-deposit-queue-graph.ts index 0e14c885..9f36373d 100644 --- a/features/view-keys/deposit-queue/use-deposit-queue-graph.ts +++ b/features/view-keys/deposit-queue/use-deposit-queue-graph.ts @@ -79,10 +79,13 @@ export const useDepositQueueGraph = (fullView = false) => { const addedSize = ccc(added, queue.add(active)); const limitOffset = extraLow ? 8 : extraHigh ? 95 : cc(capacity); - const koef = queue - .mul(100) - .div(batches?.summ || queue) - .toNumber(); + const koef = + batches?.summ.isZero() || queue.isZero() + ? One.mul(100) + : queue + .mul(100) + .div(batches?.summ || queue) + .toNumber(); const yourBatches = mergeBatches( batches?.list.map((batch) => { From 3eb66a361f1d33c5647ad1b4b26bae5ae596485d Mon Sep 17 00:00:00 2001 From: exromany Date: Tue, 18 Mar 2025 13:28:28 +0300 Subject: [PATCH 08/58] chore: disable etherscan link hoodi --- .../external-icon-link/etherscan-address-link.tsx | 6 ++++++ shared/components/tx-link-etherscan/tx-link-etherscan.tsx | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/shared/components/external-icon-link/etherscan-address-link.tsx b/shared/components/external-icon-link/etherscan-address-link.tsx index 21fd787c..e5d68dee 100644 --- a/shared/components/external-icon-link/etherscan-address-link.tsx +++ b/shared/components/external-icon-link/etherscan-address-link.tsx @@ -4,6 +4,7 @@ import { MATOMO_CLICK_EVENTS_TYPES } from 'consts/matomo-click-events'; import { FC } from 'react'; import { useAccount } from 'shared/hooks'; import { MatomoLink } from '../matomo-link/matomo-link'; +import { CHAINS } from '@lido-sdk/constants'; type Props = { address: string; @@ -11,6 +12,11 @@ type Props = { export const EtherscanAddressLink: FC = ({ address }) => { const { chainId } = useAccount(); + + if (chainId === CHAINS.Hoodi) { + return null; + } + const href = getEtherscanAddressLink(chainId ?? 0, address); return ( diff --git a/shared/components/tx-link-etherscan/tx-link-etherscan.tsx b/shared/components/tx-link-etherscan/tx-link-etherscan.tsx index cd5c881c..354eb9c0 100644 --- a/shared/components/tx-link-etherscan/tx-link-etherscan.tsx +++ b/shared/components/tx-link-etherscan/tx-link-etherscan.tsx @@ -3,6 +3,7 @@ import { getEtherscanTxLink } from '@lido-sdk/helpers'; import { MatomoLink } from '../matomo-link/matomo-link'; import { MATOMO_CLICK_EVENTS_TYPES } from 'consts/matomo-click-events'; import { FC } from 'react'; +import { CHAINS } from '@lido-sdk/constants'; type TxLinkEtherscanProps = { text?: string; @@ -17,6 +18,10 @@ export const TxLinkEtherscan: FC = ({ if (!txHash) return null; + if (chainId === CHAINS.Hoodi) { + return null; + } + return ( Date: Tue, 18 Mar 2025 13:39:03 +0300 Subject: [PATCH 09/58] chore: disable keys-api --- consts/external-links.ts | 125 +++++++++--------- shared/hooks/use-network-duplicates.ts | 2 + shared/hooks/use-operator-in-other-module.ts | 6 +- .../keys/validate/check-network-duplicates.ts | 11 +- 4 files changed, 76 insertions(+), 68 deletions(-) diff --git a/consts/external-links.ts b/consts/external-links.ts index c2315340..6316369e 100644 --- a/consts/external-links.ts +++ b/consts/external-links.ts @@ -36,71 +36,72 @@ type ExternalLinksConstants = { surveyApi: string; }; -export const EXTERNAL_LINKS_BY_NETWORK: Record = { - [CHAINS.Mainnet]: { - earlyAdoptionTree: - 'https://raw.githubusercontent.com/lidofinance/community-staking-module/v1.0/artifacts/mainnet/early-adoption/merkle-tree.json', - rewardsTree: - 'https://raw.githubusercontent.com/lidofinance/csm-rewards/mainnet/tree.json', - earlyAdoptionSources: - 'https://github.com/lidofinance/community-staking-module/blob/v1.0/artifacts/mainnet/early-adoption/addresses.json', - earlyAdoptionAbout: - 'https://operatorportal.lido.fi/modules/community-staking-module#block-ef60a1fa96ae4c7995dd7794de2a3e22', - feedbackForm: 'https://forms.gle/GL9RYeV2g4px58Sv8', - stakeWidget: 'https://stake.lido.fi', +export const EXTERNAL_LINKS_BY_NETWORK: Record = + { + [CHAINS.Mainnet]: { + earlyAdoptionTree: + 'https://raw.githubusercontent.com/lidofinance/community-staking-module/v1.0/artifacts/mainnet/early-adoption/merkle-tree.json', + rewardsTree: + 'https://raw.githubusercontent.com/lidofinance/csm-rewards/mainnet/tree.json', + earlyAdoptionSources: + 'https://github.com/lidofinance/community-staking-module/blob/v1.0/artifacts/mainnet/early-adoption/addresses.json', + earlyAdoptionAbout: + 'https://operatorportal.lido.fi/modules/community-staking-module#block-ef60a1fa96ae4c7995dd7794de2a3e22', + feedbackForm: 'https://forms.gle/GL9RYeV2g4px58Sv8', + stakeWidget: 'https://stake.lido.fi', - feesMonitoring: 'https://fees-monitoring.lido.fi', - operatorsWidget: 'https://operators.lido.fi', - beaconchain: 'https://beaconcha.in', - beaconchainDashboard: 'https://v2-beta-mainnet.beaconcha.in/dashboard', - ratedExplorer: 'https://explorer.rated.network', - ethseerDashboard: 'https://ethseer.io', - subscribeEvents: 'https://docs.lido.fi/staking-modules/csm/guides/events', - keysApi: 'https://keys-api.lido.fi', - surveyApi: 'https://csm-surveys-api-mainnet.up.railway.app', - }, - [CHAINS.Holesky]: { - earlyAdoptionTree: - 'https://raw.githubusercontent.com/lidofinance/community-staking-module/v1.0/artifacts/holesky/early-adoption/merkle-tree.json', - rewardsTree: - 'https://raw.githubusercontent.com/lidofinance/csm-rewards/holesky/tree.json', - earlyAdoptionSources: - 'https://github.com/lidofinance/community-staking-module/blob/v1.0/artifacts/holesky/early-adoption/addresses.json', - earlyAdoptionAbout: - 'https://operatorportal.lido.fi/modules/community-staking-module#block-ef60a1fa96ae4c7995dd7794de2a3e22', - feedbackForm: 'https://forms.gle/ZBUqbykaZokJLf4M7', - stakeWidget: 'https://stake-holesky.testnet.fi', + feesMonitoring: 'https://fees-monitoring.lido.fi', + operatorsWidget: 'https://operators.lido.fi', + beaconchain: 'https://beaconcha.in', + beaconchainDashboard: 'https://v2-beta-mainnet.beaconcha.in/dashboard', + ratedExplorer: 'https://explorer.rated.network', + ethseerDashboard: 'https://ethseer.io', + subscribeEvents: 'https://docs.lido.fi/staking-modules/csm/guides/events', + keysApi: 'https://keys-api.lido.fi', + surveyApi: 'https://csm-surveys-api-mainnet.up.railway.app', + }, + [CHAINS.Holesky]: { + earlyAdoptionTree: + 'https://raw.githubusercontent.com/lidofinance/community-staking-module/v1.0/artifacts/holesky/early-adoption/merkle-tree.json', + rewardsTree: + 'https://raw.githubusercontent.com/lidofinance/csm-rewards/holesky/tree.json', + earlyAdoptionSources: + 'https://github.com/lidofinance/community-staking-module/blob/v1.0/artifacts/holesky/early-adoption/addresses.json', + earlyAdoptionAbout: + 'https://operatorportal.lido.fi/modules/community-staking-module#block-ef60a1fa96ae4c7995dd7794de2a3e22', + feedbackForm: 'https://forms.gle/ZBUqbykaZokJLf4M7', + stakeWidget: 'https://stake-holesky.testnet.fi', - feesMonitoring: 'https://fees-monitoring-holesky.testnet.fi', - operatorsWidget: 'https://operators-holesky.testnet.fi', - beaconchain: 'https://holesky.beaconcha.in', - beaconchainDashboard: 'https://v2-beta-holesky.beaconcha.in/dashboard', - ratedExplorer: 'https://explorer.rated.network', - subscribeEvents: 'https://docs.lido.fi/staking-modules/csm/guides/events', - keysApi: 'https://keys-api-holesky.testnet.fi', - surveyApi: '', - }, - // FIXME: links - [CHAINS.Hoodi]: { - earlyAdoptionTree: '', - rewardsTree: '', - earlyAdoptionSources: - 'https://github.com/lidofinance/community-staking-module/blob/v1.0/artifacts/holesky/early-adoption/addresses.json', - earlyAdoptionAbout: - 'https://operatorportal.lido.fi/modules/community-staking-module#block-ef60a1fa96ae4c7995dd7794de2a3e22', - feedbackForm: 'https://forms.gle/ZBUqbykaZokJLf4M7', - stakeWidget: 'https://stake-holesky.testnet.fi', + feesMonitoring: 'https://fees-monitoring-holesky.testnet.fi', + operatorsWidget: 'https://operators-holesky.testnet.fi', + beaconchain: 'https://holesky.beaconcha.in', + beaconchainDashboard: 'https://v2-beta-holesky.beaconcha.in/dashboard', + ratedExplorer: 'https://explorer.rated.network', + subscribeEvents: 'https://docs.lido.fi/staking-modules/csm/guides/events', + keysApi: 'https://keys-api-holesky.testnet.fi', + surveyApi: '', + }, + // FIXME: links + [CHAINS.Hoodi]: { + earlyAdoptionTree: '', + rewardsTree: '', + earlyAdoptionSources: + 'https://github.com/lidofinance/community-staking-module/blob/v1.0/artifacts/holesky/early-adoption/addresses.json', + earlyAdoptionAbout: + 'https://operatorportal.lido.fi/modules/community-staking-module#block-ef60a1fa96ae4c7995dd7794de2a3e22', + feedbackForm: 'https://forms.gle/ZBUqbykaZokJLf4M7', + stakeWidget: 'https://stake-holesky.testnet.fi', - feesMonitoring: 'https://fees-monitoring-holesky.testnet.fi', - operatorsWidget: 'https://operators-holesky.testnet.fi', - beaconchain: 'https://holesky.beaconcha.in', - beaconchainDashboard: 'https://v2-beta-holesky.beaconcha.in/dashboard', - ratedExplorer: 'https://explorer.rated.network', - subscribeEvents: 'https://docs.lido.fi/staking-modules/csm/guides/events', - keysApi: 'http://hr6vb81d1ndsx-hoodi-keys-api.valset-01.testnet.fi', - surveyApi: 'https://csm-surveys-api-testnet.up.railway.app', - }, -}; + feesMonitoring: 'https://fees-monitoring-holesky.testnet.fi', + operatorsWidget: 'https://operators-holesky.testnet.fi', + beaconchain: 'https://holesky.beaconcha.in', + beaconchainDashboard: 'https://v2-beta-holesky.beaconcha.in/dashboard', + ratedExplorer: 'https://explorer.rated.network', + subscribeEvents: 'https://docs.lido.fi/staking-modules/csm/guides/events', + keysApi: 'http://hr6vb81d1ndsx-hoodi-keys-api.valset-01.testnet.fi', + surveyApi: 'https://csm-surveys-api-testnet.up.railway.app', + }, + }; export const getExternalLinks = ( chainId: CHAINS | undefined = config.defaultChain, diff --git a/shared/hooks/use-network-duplicates.ts b/shared/hooks/use-network-duplicates.ts index ea1f5a95..e6bead31 100644 --- a/shared/hooks/use-network-duplicates.ts +++ b/shared/hooks/use-network-duplicates.ts @@ -91,6 +91,8 @@ export const useNetworkDuplicates = (config = STRATEGY_CONSTANT) => { ['no-keys', nodeOperatorId, chainId], useCallback(async () => { invariant(nodeOperatorId, 'NodeOperatorId is not defined'); + if (!keysApi) return []; + const moduleId = getCsmConstants(chainId).stakingModuleId; const keys = await getKeys(keysApi, moduleId, nodeOperatorId); const rawKeys = await findKeys( diff --git a/shared/hooks/use-operator-in-other-module.ts b/shared/hooks/use-operator-in-other-module.ts index 95c8d30f..98f65e7d 100644 --- a/shared/hooks/use-operator-in-other-module.ts +++ b/shared/hooks/use-operator-in-other-module.ts @@ -73,7 +73,11 @@ const useSROperators = () => { return { operators, modules }; }; - return useLidoSWR(['sr-operators', keysApi], fetcher, STRATEGY_IMMUTABLE); + return useLidoSWR( + ['sr-operators', keysApi], + keysApi ? fetcher : null, + STRATEGY_IMMUTABLE, + ); }; export const useOperatorInOtherModule = () => { diff --git a/shared/keys/validate/check-network-duplicates.ts b/shared/keys/validate/check-network-duplicates.ts index 0981b4bc..016fed0d 100644 --- a/shared/keys/validate/check-network-duplicates.ts +++ b/shared/keys/validate/check-network-duplicates.ts @@ -1,10 +1,9 @@ -import { DepositData } from 'types'; -import { TRIM_LENGTH } from './constants'; import { trimAddress } from '@lidofinance/address'; -import { trimOx } from '../utils'; -import { HexString } from '../types'; -import { CHAINS } from '@lido-sdk/constants'; import { getExternalLinks } from 'consts/external-links'; +import { DepositData } from 'types'; +import { HexString } from '../types'; +import { trimOx } from '../utils'; +import { TRIM_LENGTH } from './constants'; type ResponseData = { data: { @@ -23,6 +22,8 @@ const findDuplicate = async (pubkeys: HexString[]) => { // TODO: timeout // TODO: cache const { keysApi } = getExternalLinks(); + if (!keysApi) return; + const response = await fetch(`${keysApi}/v1/keys/find`, { method: 'post', body: JSON.stringify({ pubkeys }), From 1d007ad854aeb1cf563ee1e5fd2042b384ec80aa Mon Sep 17 00:00:00 2001 From: exromany Date: Tue, 18 Mar 2025 13:58:42 +0300 Subject: [PATCH 10/58] fix: dashboard keys if keysCount = zero --- features/dashboard/keys/keys-section.tsx | 2 +- shared/hooks/useNodeOperatorKeys.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/features/dashboard/keys/keys-section.tsx b/features/dashboard/keys/keys-section.tsx index de7e2680..c3eea72f 100644 --- a/features/dashboard/keys/keys-section.tsx +++ b/features/dashboard/keys/keys-section.tsx @@ -103,7 +103,7 @@ export const KeysSection: FC = () => { tooltip="Keys that have already exited and withdrawn" /> - {keys?.length && ( + {!!keys?.length && ( Date: Tue, 18 Mar 2025 16:44:09 +0300 Subject: [PATCH 11/58] fix: faq with Hoodi WC & feeRecipient --- faq/{ => holesky}/bond-1.md | 0 faq/{testnet-bond-2.md => holesky/bond-2.md} | 0 faq/{testnet-bond-3.md => holesky/bond-3.md} | 0 faq/{ => holesky}/bond-4.md | 0 faq/{testnet-bond-5.md => holesky/bond-5.md} | 0 faq/{testnet-keys-1.md => holesky/keys-1.md} | 0 faq/{ => holesky}/keys-10.md | 0 faq/{ => holesky}/keys-11.md | 0 .../keys-12.md} | 0 .../keys-13.md} | 0 faq/{ => holesky}/keys-2.md | 0 faq/{testnet-keys-3.md => holesky/keys-3.md} | 0 .../keys-3a.md} | 0 faq/{testnet-keys-4.md => holesky/keys-4.md} | 0 .../keys-4a.md} | 0 faq/{ => holesky}/keys-5.md | 0 faq/{testnet-keys-6.md => holesky/keys-6.md} | 0 faq/{ => holesky}/keys-7.md | 0 faq/{ => holesky}/keys-8.md | 0 faq/{ => holesky}/keys-9.md | 0 faq/{ => holesky}/locked-1.md | 0 faq/{ => holesky}/locked-2.md | 0 faq/{ => holesky}/locked-3.md | 0 faq/{ => holesky}/main-1.md | 0 faq/{ => holesky}/main-2.md | 0 faq/{ => holesky}/main-3.md | 0 faq/{ => holesky}/main-4.md | 0 faq/{ => holesky}/main-5.md | 0 faq/{ => holesky}/main-6.md | 0 faq/{testnet-main-7.md => holesky/main-7.md} | 0 .../main-7a.md} | 0 faq/{testnet-main-8.md => holesky/main-8.md} | 0 faq/{ => holesky}/roles-1.md | 0 faq/{ => holesky}/roles-2.md | 0 faq/{ => holesky}/roles-3.md | 0 faq/{ => holesky}/roles-4.md | 0 faq/{ => holesky}/roles-5.md | 0 faq/{testnet-bond-1.md => hoodi/bond-1.md} | 0 faq/hoodi/bond-2.md | 7 ++++++ faq/hoodi/bond-3.md | 9 ++++++++ faq/{testnet-bond-4.md => hoodi/bond-4.md} | 0 faq/hoodi/bond-5.md | 13 +++++++++++ faq/hoodi/keys-1.md | 13 +++++++++++ faq/{testnet-keys-10.md => hoodi/keys-10.md} | 0 faq/{testnet-keys-11.md => hoodi/keys-11.md} | 0 faq/hoodi/keys-12.md | 5 ++++ faq/hoodi/keys-13.md | 18 +++++++++++++++ faq/{testnet-keys-2.md => hoodi/keys-2.md} | 0 faq/hoodi/keys-3.md | 13 +++++++++++ faq/hoodi/keys-3a.md | 13 +++++++++++ faq/hoodi/keys-4.md | 10 ++++++++ faq/hoodi/keys-4a.md | 10 ++++++++ faq/{testnet-keys-5.md => hoodi/keys-5.md} | 0 faq/hoodi/keys-6.md | 16 +++++++++++++ faq/{testnet-keys-7.md => hoodi/keys-7.md} | 0 faq/{testnet-keys-8.md => hoodi/keys-8.md} | 0 faq/{testnet-keys-9.md => hoodi/keys-9.md} | 0 .../locked-1.md} | 0 .../locked-2.md} | 0 .../locked-3.md} | 0 faq/{testnet-main-1.md => hoodi/main-1.md} | 0 faq/{testnet-main-2.md => hoodi/main-2.md} | 0 faq/{testnet-main-3.md => hoodi/main-3.md} | 0 faq/{testnet-main-4.md => hoodi/main-4.md} | 0 faq/{testnet-main-5.md => hoodi/main-5.md} | 0 faq/{testnet-main-6.md => hoodi/main-6.md} | 0 faq/hoodi/main-7.md | 12 ++++++++++ faq/hoodi/main-7a.md | 12 ++++++++++ faq/hoodi/main-8.md | 5 ++++ faq/{testnet-roles-1.md => hoodi/roles-1.md} | 0 faq/{testnet-roles-2.md => hoodi/roles-2.md} | 0 faq/{testnet-roles-3.md => hoodi/roles-3.md} | 0 faq/{testnet-roles-4.md => hoodi/roles-4.md} | 0 faq/{testnet-roles-5.md => hoodi/roles-5.md} | 0 faq/mainnet/bond-1.md | 8 +++++++ faq/{ => mainnet}/bond-2.md | 0 faq/{ => mainnet}/bond-3.md | 0 faq/mainnet/bond-4.md | 9 ++++++++ faq/{ => mainnet}/bond-5.md | 0 faq/{ => mainnet}/keys-1.md | 0 faq/mainnet/keys-10.md | 10 ++++++++ faq/mainnet/keys-11.md | 6 +++++ faq/{ => mainnet}/keys-12.md | 0 faq/{ => mainnet}/keys-13.md | 0 faq/mainnet/keys-2.md | 11 +++++++++ faq/{ => mainnet}/keys-3.md | 0 faq/{ => mainnet}/keys-3a.md | 0 faq/{ => mainnet}/keys-4.md | 0 faq/{ => mainnet}/keys-4a.md | 0 faq/mainnet/keys-5.md | 5 ++++ faq/{ => mainnet}/keys-6.md | 0 faq/mainnet/keys-7.md | 5 ++++ faq/mainnet/keys-8.md | 5 ++++ faq/mainnet/keys-9.md | 5 ++++ faq/mainnet/locked-1.md | 5 ++++ faq/mainnet/locked-2.md | 7 ++++++ faq/mainnet/locked-3.md | 5 ++++ faq/mainnet/main-1.md | 9 ++++++++ faq/mainnet/main-2.md | 10 ++++++++ faq/mainnet/main-3.md | 8 +++++++ faq/mainnet/main-4.md | 12 ++++++++++ faq/mainnet/main-5.md | 5 ++++ faq/mainnet/main-6.md | 11 +++++++++ faq/{ => mainnet}/main-7.md | 0 faq/{ => mainnet}/main-7a.md | 0 faq/{ => mainnet}/main-8.md | 0 faq/mainnet/roles-1.md | 23 +++++++++++++++++++ faq/mainnet/roles-2.md | 7 ++++++ faq/mainnet/roles-3.md | 5 ++++ faq/mainnet/roles-4.md | 5 ++++ faq/mainnet/roles-5.md | 5 ++++ lib/getFaq.ts | 14 +++++------ 112 files changed, 343 insertions(+), 8 deletions(-) rename faq/{ => holesky}/bond-1.md (100%) rename faq/{testnet-bond-2.md => holesky/bond-2.md} (100%) rename faq/{testnet-bond-3.md => holesky/bond-3.md} (100%) rename faq/{ => holesky}/bond-4.md (100%) rename faq/{testnet-bond-5.md => holesky/bond-5.md} (100%) rename faq/{testnet-keys-1.md => holesky/keys-1.md} (100%) rename faq/{ => holesky}/keys-10.md (100%) rename faq/{ => holesky}/keys-11.md (100%) rename faq/{testnet-keys-12.md => holesky/keys-12.md} (100%) rename faq/{testnet-keys-13.md => holesky/keys-13.md} (100%) rename faq/{ => holesky}/keys-2.md (100%) rename faq/{testnet-keys-3.md => holesky/keys-3.md} (100%) rename faq/{testnet-keys-3a.md => holesky/keys-3a.md} (100%) rename faq/{testnet-keys-4.md => holesky/keys-4.md} (100%) rename faq/{testnet-keys-4a.md => holesky/keys-4a.md} (100%) rename faq/{ => holesky}/keys-5.md (100%) rename faq/{testnet-keys-6.md => holesky/keys-6.md} (100%) rename faq/{ => holesky}/keys-7.md (100%) rename faq/{ => holesky}/keys-8.md (100%) rename faq/{ => holesky}/keys-9.md (100%) rename faq/{ => holesky}/locked-1.md (100%) rename faq/{ => holesky}/locked-2.md (100%) rename faq/{ => holesky}/locked-3.md (100%) rename faq/{ => holesky}/main-1.md (100%) rename faq/{ => holesky}/main-2.md (100%) rename faq/{ => holesky}/main-3.md (100%) rename faq/{ => holesky}/main-4.md (100%) rename faq/{ => holesky}/main-5.md (100%) rename faq/{ => holesky}/main-6.md (100%) rename faq/{testnet-main-7.md => holesky/main-7.md} (100%) rename faq/{testnet-main-7a.md => holesky/main-7a.md} (100%) rename faq/{testnet-main-8.md => holesky/main-8.md} (100%) rename faq/{ => holesky}/roles-1.md (100%) rename faq/{ => holesky}/roles-2.md (100%) rename faq/{ => holesky}/roles-3.md (100%) rename faq/{ => holesky}/roles-4.md (100%) rename faq/{ => holesky}/roles-5.md (100%) rename faq/{testnet-bond-1.md => hoodi/bond-1.md} (100%) create mode 100644 faq/hoodi/bond-2.md create mode 100644 faq/hoodi/bond-3.md rename faq/{testnet-bond-4.md => hoodi/bond-4.md} (100%) create mode 100644 faq/hoodi/bond-5.md create mode 100644 faq/hoodi/keys-1.md rename faq/{testnet-keys-10.md => hoodi/keys-10.md} (100%) rename faq/{testnet-keys-11.md => hoodi/keys-11.md} (100%) create mode 100644 faq/hoodi/keys-12.md create mode 100644 faq/hoodi/keys-13.md rename faq/{testnet-keys-2.md => hoodi/keys-2.md} (100%) create mode 100644 faq/hoodi/keys-3.md create mode 100644 faq/hoodi/keys-3a.md create mode 100644 faq/hoodi/keys-4.md create mode 100644 faq/hoodi/keys-4a.md rename faq/{testnet-keys-5.md => hoodi/keys-5.md} (100%) create mode 100644 faq/hoodi/keys-6.md rename faq/{testnet-keys-7.md => hoodi/keys-7.md} (100%) rename faq/{testnet-keys-8.md => hoodi/keys-8.md} (100%) rename faq/{testnet-keys-9.md => hoodi/keys-9.md} (100%) rename faq/{testnet-locked-1.md => hoodi/locked-1.md} (100%) rename faq/{testnet-locked-2.md => hoodi/locked-2.md} (100%) rename faq/{testnet-locked-3.md => hoodi/locked-3.md} (100%) rename faq/{testnet-main-1.md => hoodi/main-1.md} (100%) rename faq/{testnet-main-2.md => hoodi/main-2.md} (100%) rename faq/{testnet-main-3.md => hoodi/main-3.md} (100%) rename faq/{testnet-main-4.md => hoodi/main-4.md} (100%) rename faq/{testnet-main-5.md => hoodi/main-5.md} (100%) rename faq/{testnet-main-6.md => hoodi/main-6.md} (100%) create mode 100644 faq/hoodi/main-7.md create mode 100644 faq/hoodi/main-7a.md create mode 100644 faq/hoodi/main-8.md rename faq/{testnet-roles-1.md => hoodi/roles-1.md} (100%) rename faq/{testnet-roles-2.md => hoodi/roles-2.md} (100%) rename faq/{testnet-roles-3.md => hoodi/roles-3.md} (100%) rename faq/{testnet-roles-4.md => hoodi/roles-4.md} (100%) rename faq/{testnet-roles-5.md => hoodi/roles-5.md} (100%) create mode 100644 faq/mainnet/bond-1.md rename faq/{ => mainnet}/bond-2.md (100%) rename faq/{ => mainnet}/bond-3.md (100%) create mode 100644 faq/mainnet/bond-4.md rename faq/{ => mainnet}/bond-5.md (100%) rename faq/{ => mainnet}/keys-1.md (100%) create mode 100644 faq/mainnet/keys-10.md create mode 100644 faq/mainnet/keys-11.md rename faq/{ => mainnet}/keys-12.md (100%) rename faq/{ => mainnet}/keys-13.md (100%) create mode 100644 faq/mainnet/keys-2.md rename faq/{ => mainnet}/keys-3.md (100%) rename faq/{ => mainnet}/keys-3a.md (100%) rename faq/{ => mainnet}/keys-4.md (100%) rename faq/{ => mainnet}/keys-4a.md (100%) create mode 100644 faq/mainnet/keys-5.md rename faq/{ => mainnet}/keys-6.md (100%) create mode 100644 faq/mainnet/keys-7.md create mode 100644 faq/mainnet/keys-8.md create mode 100644 faq/mainnet/keys-9.md create mode 100644 faq/mainnet/locked-1.md create mode 100644 faq/mainnet/locked-2.md create mode 100644 faq/mainnet/locked-3.md create mode 100644 faq/mainnet/main-1.md create mode 100644 faq/mainnet/main-2.md create mode 100644 faq/mainnet/main-3.md create mode 100644 faq/mainnet/main-4.md create mode 100644 faq/mainnet/main-5.md create mode 100644 faq/mainnet/main-6.md rename faq/{ => mainnet}/main-7.md (100%) rename faq/{ => mainnet}/main-7a.md (100%) rename faq/{ => mainnet}/main-8.md (100%) create mode 100644 faq/mainnet/roles-1.md create mode 100644 faq/mainnet/roles-2.md create mode 100644 faq/mainnet/roles-3.md create mode 100644 faq/mainnet/roles-4.md create mode 100644 faq/mainnet/roles-5.md diff --git a/faq/bond-1.md b/faq/holesky/bond-1.md similarity index 100% rename from faq/bond-1.md rename to faq/holesky/bond-1.md diff --git a/faq/testnet-bond-2.md b/faq/holesky/bond-2.md similarity index 100% rename from faq/testnet-bond-2.md rename to faq/holesky/bond-2.md diff --git a/faq/testnet-bond-3.md b/faq/holesky/bond-3.md similarity index 100% rename from faq/testnet-bond-3.md rename to faq/holesky/bond-3.md diff --git a/faq/bond-4.md b/faq/holesky/bond-4.md similarity index 100% rename from faq/bond-4.md rename to faq/holesky/bond-4.md diff --git a/faq/testnet-bond-5.md b/faq/holesky/bond-5.md similarity index 100% rename from faq/testnet-bond-5.md rename to faq/holesky/bond-5.md diff --git a/faq/testnet-keys-1.md b/faq/holesky/keys-1.md similarity index 100% rename from faq/testnet-keys-1.md rename to faq/holesky/keys-1.md diff --git a/faq/keys-10.md b/faq/holesky/keys-10.md similarity index 100% rename from faq/keys-10.md rename to faq/holesky/keys-10.md diff --git a/faq/keys-11.md b/faq/holesky/keys-11.md similarity index 100% rename from faq/keys-11.md rename to faq/holesky/keys-11.md diff --git a/faq/testnet-keys-12.md b/faq/holesky/keys-12.md similarity index 100% rename from faq/testnet-keys-12.md rename to faq/holesky/keys-12.md diff --git a/faq/testnet-keys-13.md b/faq/holesky/keys-13.md similarity index 100% rename from faq/testnet-keys-13.md rename to faq/holesky/keys-13.md diff --git a/faq/keys-2.md b/faq/holesky/keys-2.md similarity index 100% rename from faq/keys-2.md rename to faq/holesky/keys-2.md diff --git a/faq/testnet-keys-3.md b/faq/holesky/keys-3.md similarity index 100% rename from faq/testnet-keys-3.md rename to faq/holesky/keys-3.md diff --git a/faq/testnet-keys-3a.md b/faq/holesky/keys-3a.md similarity index 100% rename from faq/testnet-keys-3a.md rename to faq/holesky/keys-3a.md diff --git a/faq/testnet-keys-4.md b/faq/holesky/keys-4.md similarity index 100% rename from faq/testnet-keys-4.md rename to faq/holesky/keys-4.md diff --git a/faq/testnet-keys-4a.md b/faq/holesky/keys-4a.md similarity index 100% rename from faq/testnet-keys-4a.md rename to faq/holesky/keys-4a.md diff --git a/faq/keys-5.md b/faq/holesky/keys-5.md similarity index 100% rename from faq/keys-5.md rename to faq/holesky/keys-5.md diff --git a/faq/testnet-keys-6.md b/faq/holesky/keys-6.md similarity index 100% rename from faq/testnet-keys-6.md rename to faq/holesky/keys-6.md diff --git a/faq/keys-7.md b/faq/holesky/keys-7.md similarity index 100% rename from faq/keys-7.md rename to faq/holesky/keys-7.md diff --git a/faq/keys-8.md b/faq/holesky/keys-8.md similarity index 100% rename from faq/keys-8.md rename to faq/holesky/keys-8.md diff --git a/faq/keys-9.md b/faq/holesky/keys-9.md similarity index 100% rename from faq/keys-9.md rename to faq/holesky/keys-9.md diff --git a/faq/locked-1.md b/faq/holesky/locked-1.md similarity index 100% rename from faq/locked-1.md rename to faq/holesky/locked-1.md diff --git a/faq/locked-2.md b/faq/holesky/locked-2.md similarity index 100% rename from faq/locked-2.md rename to faq/holesky/locked-2.md diff --git a/faq/locked-3.md b/faq/holesky/locked-3.md similarity index 100% rename from faq/locked-3.md rename to faq/holesky/locked-3.md diff --git a/faq/main-1.md b/faq/holesky/main-1.md similarity index 100% rename from faq/main-1.md rename to faq/holesky/main-1.md diff --git a/faq/main-2.md b/faq/holesky/main-2.md similarity index 100% rename from faq/main-2.md rename to faq/holesky/main-2.md diff --git a/faq/main-3.md b/faq/holesky/main-3.md similarity index 100% rename from faq/main-3.md rename to faq/holesky/main-3.md diff --git a/faq/main-4.md b/faq/holesky/main-4.md similarity index 100% rename from faq/main-4.md rename to faq/holesky/main-4.md diff --git a/faq/main-5.md b/faq/holesky/main-5.md similarity index 100% rename from faq/main-5.md rename to faq/holesky/main-5.md diff --git a/faq/main-6.md b/faq/holesky/main-6.md similarity index 100% rename from faq/main-6.md rename to faq/holesky/main-6.md diff --git a/faq/testnet-main-7.md b/faq/holesky/main-7.md similarity index 100% rename from faq/testnet-main-7.md rename to faq/holesky/main-7.md diff --git a/faq/testnet-main-7a.md b/faq/holesky/main-7a.md similarity index 100% rename from faq/testnet-main-7a.md rename to faq/holesky/main-7a.md diff --git a/faq/testnet-main-8.md b/faq/holesky/main-8.md similarity index 100% rename from faq/testnet-main-8.md rename to faq/holesky/main-8.md diff --git a/faq/roles-1.md b/faq/holesky/roles-1.md similarity index 100% rename from faq/roles-1.md rename to faq/holesky/roles-1.md diff --git a/faq/roles-2.md b/faq/holesky/roles-2.md similarity index 100% rename from faq/roles-2.md rename to faq/holesky/roles-2.md diff --git a/faq/roles-3.md b/faq/holesky/roles-3.md similarity index 100% rename from faq/roles-3.md rename to faq/holesky/roles-3.md diff --git a/faq/roles-4.md b/faq/holesky/roles-4.md similarity index 100% rename from faq/roles-4.md rename to faq/holesky/roles-4.md diff --git a/faq/roles-5.md b/faq/holesky/roles-5.md similarity index 100% rename from faq/roles-5.md rename to faq/holesky/roles-5.md diff --git a/faq/testnet-bond-1.md b/faq/hoodi/bond-1.md similarity index 100% rename from faq/testnet-bond-1.md rename to faq/hoodi/bond-1.md diff --git a/faq/hoodi/bond-2.md b/faq/hoodi/bond-2.md new file mode 100644 index 00000000..1697d259 --- /dev/null +++ b/faq/hoodi/bond-2.md @@ -0,0 +1,7 @@ +--- +title: How often do I get rewards? +--- + +**Node Operator rewards** on testnet are calculated and made claimable by the CSM Oracle **every 7 days**. Rewards do not have to be claimed during every reporting frame, and can be left to accumulate to be claimed later. + +**Bond rebase part** of the rewards come from stETH being a rebasing token and the bond being stored in stETH. After each Accounting Oracle report that happens on testnet **every 12 epochs (1hr 20min)**, the share rate changes. Hence, the same amount of stETH shares will now be equal to a bigger stETH token balance. diff --git a/faq/hoodi/bond-3.md b/faq/hoodi/bond-3.md new file mode 100644 index 00000000..de49ca7e --- /dev/null +++ b/faq/hoodi/bond-3.md @@ -0,0 +1,9 @@ +--- +title: Why didn’t I get rewards? +anchor: why-did-not-i-get-rewards +--- + +There are two main reasons of you getting no reward within a frame: + +1. If your validator’s performance was below the threshold within the CSM Performance Oracle frame (7 days for testnet) the validator does not receive rewards for the given frame. Read more about [the CSM Performance Oracle](https://operatorportal.lido.fi/modules/community-staking-module#block-c6dc8d00f13243fcb17de3fa07ecc52c). +2. [Your Node Operator has stuck keys](https://operatorportal.lido.fi/modules/community-staking-module#block-0ed61a4c0a5a439bbb4be20e814b4e38) due to not exiting a validator requested for exit timely. diff --git a/faq/testnet-bond-4.md b/faq/hoodi/bond-4.md similarity index 100% rename from faq/testnet-bond-4.md rename to faq/hoodi/bond-4.md diff --git a/faq/hoodi/bond-5.md b/faq/hoodi/bond-5.md new file mode 100644 index 00000000..90af7696 --- /dev/null +++ b/faq/hoodi/bond-5.md @@ -0,0 +1,13 @@ +--- +title: How to claim ETH using a withdrawal NFT +anchor: how-to-claim-eth +--- + +Claiming bond and rewards in a form of ETH constitutes an stETH withdrawal process (unstake). + +The withdrawal process consists of several steps you need to do: + +- **Submit a withdrawal request** by choosing ETH as a token for bond/rewards claim. As a result of this step, you will receive a withdrawal NFT. +- **Claim your ETH** after request fulfilment. The fulfilment process takes 1-5 days (or longer), [depending on a variety of factors](https://help.lido.fi/en/articles/7858315-how-long-does-an-ethereum-withdrawal-take). To know if your ETH is ready to be claimed you, can check its status on the [Claim page](https://stake-holesky.testnet.fi/withdrawals/claim). If your request is marked as “**Ready to claim**”, it is time for you to get your ETH back. + +For more information about withdrawals, [follow the page](https://help.lido.fi/en/collections/3993867-ethereum-withdrawals). diff --git a/faq/hoodi/keys-1.md b/faq/hoodi/keys-1.md new file mode 100644 index 00000000..08ec2b0e --- /dev/null +++ b/faq/hoodi/keys-1.md @@ -0,0 +1,13 @@ +--- +title: How to set up a validator for CSM testnet? +--- + +A detailed guide on preparing all the validation tools for CSM can be found [here](https://dvt-homestaker.stakesaurus.com/bonded-validators-setup/lido-csm). + +A shorter flow of setting up a CSM validator for **testnet** looks as follows: + +1. [Generate new validator keys](https://dvt-homestaker.stakesaurus.com/keystore-generation-and-mev-boost/validator-key-generation) setting the `withdrawal_address` to the [Lido Withdrawal Vault](https://hoodi.cloud.blockscout.com/address/0x4473dCDDbf77679A643BdB654dbd86D67F8d32f2) on **Hoodi:** [`0x4473dCDDbf77679A643BdB654dbd86D67F8d32f2`](https://hoodi.cloud.blockscout.com/address/0x4473dCDDbf77679A643BdB654dbd86D67F8d32f2) and specify the deposit amount of 32 ETH (do **NOT** make a deposit) +2. [Configure your validator client](https://dvt-homestaker.stakesaurus.com/native-solo-staking-setup/validator-client-setup) (and/or beacon node) setting the `fee_recipient` flag to the designated fee recipient address (Lido Execution Layer Rewards Vault) on **Hoodi:** [`0x9b108015fe433F173696Af3Aa0CF7CDb3E104258`](https://hoodi.cloud.blockscout.com/address/0x9b108015fe433F173696Af3Aa0CF7CDb3E104258) and import the newly generated CSM keystores +3. **Do not setup any MEV-Boost** +4. [Upload the newly generated deposit data](https://dvt-homestaker.stakesaurus.com/bonded-validators-setup/lido-csm/upload-remove-view-validator-keys) file pertaining to your CSM keystores onto [the Lido CSM Widget](https://csm.testnet.fi/) and provide the required bond amount in Hoodi ETH/stETH/wstETH. Before uploading, make sure that nodes are synced, running, and ready for the validator activation. +5. Wait for your CSM validator keys to be deposited through the protocol and make sure your node remains online in the meantime! diff --git a/faq/testnet-keys-10.md b/faq/hoodi/keys-10.md similarity index 100% rename from faq/testnet-keys-10.md rename to faq/hoodi/keys-10.md diff --git a/faq/testnet-keys-11.md b/faq/hoodi/keys-11.md similarity index 100% rename from faq/testnet-keys-11.md rename to faq/hoodi/keys-11.md diff --git a/faq/hoodi/keys-12.md b/faq/hoodi/keys-12.md new file mode 100644 index 00000000..1b15238d --- /dev/null +++ b/faq/hoodi/keys-12.md @@ -0,0 +1,5 @@ +--- +title: What to do in case of technical issues? +--- + +For community assistance, join the "[CSM-testnet](https://discord.com/channels/761182643269795850/1255114351120089148)" channel on the [Lido Discord server](https://discord.com/invite/lido) to seek advice and guidance. diff --git a/faq/hoodi/keys-13.md b/faq/hoodi/keys-13.md new file mode 100644 index 00000000..2dd0f972 --- /dev/null +++ b/faq/hoodi/keys-13.md @@ -0,0 +1,18 @@ +--- +title: What is the CSM Stake Share Limit? +anchor: stake-share-limit +--- + +The stake share limit is a parameter defined for each Staking Module based on its risk profile. It determines the percentage of the total stake in the Lido Protocol that can be allocated to the module. Currently, the stake share limit for CSM is set at 15%. Once CSM reaches its stake share limit, new keys can still be uploaded, but deposits to these keys may take a very long time (e.g. months), if they are deposited to at all. These factors affect the possibility of new deposits to your uploaded keys: + +- The number of keys already in the deposit queue, and the position of your keys in this queue +- The number of keys that will exit from CSM +- Changes in the total volume of stake in the Lido Protocol (both net flows as well as whether overall Lido protocol stake increases or not) + +In other words, if keys had not been deposited to before CSM reached its limit, they may still be deposited to later if: + +- The overall stake volume in the Lido Protocol increases +- Keys exit from CSM, freeing up space for new keys +- The DAO decides to increase CSM's stake share limit + +While keys are awaiting deposit, Node Operators continue to receive daily bond rewards based on the bond they submitted. However, they do not receive Node Operator rewards, as the keys remain inactive until they are fully deposited. diff --git a/faq/testnet-keys-2.md b/faq/hoodi/keys-2.md similarity index 100% rename from faq/testnet-keys-2.md rename to faq/hoodi/keys-2.md diff --git a/faq/hoodi/keys-3.md b/faq/hoodi/keys-3.md new file mode 100644 index 00000000..a4c6e0db --- /dev/null +++ b/faq/hoodi/keys-3.md @@ -0,0 +1,13 @@ +--- +title: How much bond is needed? +earlyAdoption: false +anchor: how-bond-is-calculated +--- + +The initial bond requirement for the first validator for the testnet is 2 stETH. However, for [Early Adopters](https://operatorportal.lido.fi/modules/community-staking-module#block-ef60a1fa96ae4c7995dd7794de2a3e22), this amount is reduced to 1.5 stETH to incentivize early participation. + +Subsequent bond amounts depend on the total number of validators operated by the node operator and follow a specific function known as the “[bond curve](https://operatorportal.lido.fi/modules/community-staking-module#block-2d1c307d95fc4f8ab7c32b7584f795cf)”, which adjusts the bond requirement based on the operator's validator count. + +For the testnet, the values for the bond curve are the following: + +![curve.png](/assets/curve-common.png) diff --git a/faq/hoodi/keys-3a.md b/faq/hoodi/keys-3a.md new file mode 100644 index 00000000..f1ad7a06 --- /dev/null +++ b/faq/hoodi/keys-3a.md @@ -0,0 +1,13 @@ +--- +title: How much bond is needed? +earlyAdoption: true +anchor: how-bond-is-calculated +--- + +The initial bond requirement for the first validator for the testnet is 2 stETH. However, for [Early Adopters](https://operatorportal.lido.fi/modules/community-staking-module#block-ef60a1fa96ae4c7995dd7794de2a3e22), this amount is reduced to 1.5 stETH to incentivize early participation. + +Subsequent bond amounts depend on the total number of validators operated by the node operator and follow a specific function known as the “[bond curve](https://operatorportal.lido.fi/modules/community-staking-module#block-2d1c307d95fc4f8ab7c32b7584f795cf)”, which adjusts the bond requirement based on the operator's validator count. + +For the testnet, the values for the bond curve are the following: + +![curve.png](/assets/curve-ea.png) diff --git a/faq/hoodi/keys-4.md b/faq/hoodi/keys-4.md new file mode 100644 index 00000000..731c6ded --- /dev/null +++ b/faq/hoodi/keys-4.md @@ -0,0 +1,10 @@ +--- +title: What is the bond curve? +earlyAdoption: false +--- + +[The bond curve](https://operatorportal.lido.fi/modules/community-staking-module#block-2d1c307d95fc4f8ab7c32b7584f795cf) is a function that determines the amount of bond required for each subsequent validator operated by the node operator. For [Early Adopters](https://operatorportal.lido.fi/modules/community-staking-module#block-ef60a1fa96ae4c7995dd7794de2a3e22), a unique bond curve function is applied to incentivize early participation. + +For the testnet, the values for the bond curve are the following: + +![curve.png](/assets/curve-common.png) diff --git a/faq/hoodi/keys-4a.md b/faq/hoodi/keys-4a.md new file mode 100644 index 00000000..5b43be9b --- /dev/null +++ b/faq/hoodi/keys-4a.md @@ -0,0 +1,10 @@ +--- +title: What is the bond curve? +earlyAdoption: true +--- + +[The bond curve](https://operatorportal.lido.fi/modules/community-staking-module#block-2d1c307d95fc4f8ab7c32b7584f795cf) is a function that determines the amount of bond required for each subsequent validator operated by the node operator. For [Early Adopters](https://operatorportal.lido.fi/modules/community-staking-module#block-ef60a1fa96ae4c7995dd7794de2a3e22), a unique bond curve function is applied to incentivize early participation. + +For the testnet, the values for the bond curve are the following: + +![curve.png](/assets/curve-ea.png) diff --git a/faq/testnet-keys-5.md b/faq/hoodi/keys-5.md similarity index 100% rename from faq/testnet-keys-5.md rename to faq/hoodi/keys-5.md diff --git a/faq/hoodi/keys-6.md b/faq/hoodi/keys-6.md new file mode 100644 index 00000000..0c2bddda --- /dev/null +++ b/faq/hoodi/keys-6.md @@ -0,0 +1,16 @@ +--- +title: When does a validator become active? +anchor: when-validator-become-active +--- + +After key submission, and if keys have been successfully validated, two actions are required for a validator to be activated: + +1. **Deposit by Lido Protocol**: The time to deposit a validator is unpredictable and depends on factors such as total stake inflows and outflows, gas considerations, module shares, CSM deposit queue size, and the Node Operator's place in the queue. + + You can subscribe to [the important CSM events](https://docs.lido.fi/staking-modules/csm/guides/events) to stay notified about your validator being deposited to. + + Read more information about the deposits flow [here](https://operatorportal.lido.fi/modules/community-staking-module#block-90b8ff95edc64cf7a051584820219616). + +2. **Activation on Ethereum Network**: Once deposited, the validator enters the Beacon Chain activation queue. The time to activation depends on the total number of validators in the queue awaiting activation and the rate of queue processing, which varies based on the total number of active Ethereum validators. + + You can check if the keys are activated on the [Keys tab](https://csm.testnet.fi/keys) or on [beaconcha.in](http://beaconcha.in/) diff --git a/faq/testnet-keys-7.md b/faq/hoodi/keys-7.md similarity index 100% rename from faq/testnet-keys-7.md rename to faq/hoodi/keys-7.md diff --git a/faq/testnet-keys-8.md b/faq/hoodi/keys-8.md similarity index 100% rename from faq/testnet-keys-8.md rename to faq/hoodi/keys-8.md diff --git a/faq/testnet-keys-9.md b/faq/hoodi/keys-9.md similarity index 100% rename from faq/testnet-keys-9.md rename to faq/hoodi/keys-9.md diff --git a/faq/testnet-locked-1.md b/faq/hoodi/locked-1.md similarity index 100% rename from faq/testnet-locked-1.md rename to faq/hoodi/locked-1.md diff --git a/faq/testnet-locked-2.md b/faq/hoodi/locked-2.md similarity index 100% rename from faq/testnet-locked-2.md rename to faq/hoodi/locked-2.md diff --git a/faq/testnet-locked-3.md b/faq/hoodi/locked-3.md similarity index 100% rename from faq/testnet-locked-3.md rename to faq/hoodi/locked-3.md diff --git a/faq/testnet-main-1.md b/faq/hoodi/main-1.md similarity index 100% rename from faq/testnet-main-1.md rename to faq/hoodi/main-1.md diff --git a/faq/testnet-main-2.md b/faq/hoodi/main-2.md similarity index 100% rename from faq/testnet-main-2.md rename to faq/hoodi/main-2.md diff --git a/faq/testnet-main-3.md b/faq/hoodi/main-3.md similarity index 100% rename from faq/testnet-main-3.md rename to faq/hoodi/main-3.md diff --git a/faq/testnet-main-4.md b/faq/hoodi/main-4.md similarity index 100% rename from faq/testnet-main-4.md rename to faq/hoodi/main-4.md diff --git a/faq/testnet-main-5.md b/faq/hoodi/main-5.md similarity index 100% rename from faq/testnet-main-5.md rename to faq/hoodi/main-5.md diff --git a/faq/testnet-main-6.md b/faq/hoodi/main-6.md similarity index 100% rename from faq/testnet-main-6.md rename to faq/hoodi/main-6.md diff --git a/faq/hoodi/main-7.md b/faq/hoodi/main-7.md new file mode 100644 index 00000000..f1e10d2c --- /dev/null +++ b/faq/hoodi/main-7.md @@ -0,0 +1,12 @@ +--- +title: How much bond is needed? +earlyAdoption: false +--- + +The initial bond requirement for the first validator for the testnet is 2 stETH. However, for [Early Adopters](https://operatorportal.lido.fi/modules/community-staking-module#block-ef60a1fa96ae4c7995dd7794de2a3e22), this amount is reduced to 1.5 stETH to incentivize early participation. + +Subsequent bond amounts depend on the total number of validators operated by the node operator and follow a specific function known as the “[bond curve](https://operatorportal.lido.fi/modules/community-staking-module#block-2d1c307d95fc4f8ab7c32b7584f795cf)”, which adjusts the bond requirement based on the operator's validator count. + +For the testnet, the values for the bond curve are the following: + +![curve.png](/assets/curve-common.png) diff --git a/faq/hoodi/main-7a.md b/faq/hoodi/main-7a.md new file mode 100644 index 00000000..fd008e98 --- /dev/null +++ b/faq/hoodi/main-7a.md @@ -0,0 +1,12 @@ +--- +title: How much bond is needed? +earlyAdoption: true +--- + +The initial bond requirement for the first validator for the testnet is 2 stETH. However, for [Early Adopters](https://operatorportal.lido.fi/modules/community-staking-module#block-ef60a1fa96ae4c7995dd7794de2a3e22), this amount is reduced to 1.5 stETH to incentivize early participation. + +Subsequent bond amounts depend on the total number of validators operated by the node operator and follow a specific function known as the “[bond curve](https://operatorportal.lido.fi/modules/community-staking-module#block-2d1c307d95fc4f8ab7c32b7584f795cf)”, which adjusts the bond requirement based on the operator's validator count. + +For the testnet, the values for the bond curve are the following: + +![curve.png](/assets/curve-ea.png) diff --git a/faq/hoodi/main-8.md b/faq/hoodi/main-8.md new file mode 100644 index 00000000..35c06f54 --- /dev/null +++ b/faq/hoodi/main-8.md @@ -0,0 +1,5 @@ +--- +title: How can I get help? +--- + +For community assistance, join the "[CSM-testnet](https://discord.com/channels/761182643269795850/1255114351120089148)" channel on the [Lido Discord server](https://discord.com/invite/lido) to seek advice and guidance. diff --git a/faq/testnet-roles-1.md b/faq/hoodi/roles-1.md similarity index 100% rename from faq/testnet-roles-1.md rename to faq/hoodi/roles-1.md diff --git a/faq/testnet-roles-2.md b/faq/hoodi/roles-2.md similarity index 100% rename from faq/testnet-roles-2.md rename to faq/hoodi/roles-2.md diff --git a/faq/testnet-roles-3.md b/faq/hoodi/roles-3.md similarity index 100% rename from faq/testnet-roles-3.md rename to faq/hoodi/roles-3.md diff --git a/faq/testnet-roles-4.md b/faq/hoodi/roles-4.md similarity index 100% rename from faq/testnet-roles-4.md rename to faq/hoodi/roles-4.md diff --git a/faq/testnet-roles-5.md b/faq/hoodi/roles-5.md similarity index 100% rename from faq/testnet-roles-5.md rename to faq/hoodi/roles-5.md diff --git a/faq/mainnet/bond-1.md b/faq/mainnet/bond-1.md new file mode 100644 index 00000000..f4cc4ef4 --- /dev/null +++ b/faq/mainnet/bond-1.md @@ -0,0 +1,8 @@ +--- +title: What rewards do I get in CSM? +--- + +When CSM operators use the Lido protocol to run validators, they can receive two types of rewards: + +- **Node Operator rewards**: a share of rewards from staker locked stake amount, currently calculated pro-rata based on validators operated as a share of total protocol validators, with [possible reductions for bad performance](https://operatorportal.lido.fi/modules/community-staking-module#block-c6dc8d00f13243fcb17de3fa07ecc52c). +- **Bond rebase**: staking rewards pertaining to the bonded tokens (stETH). diff --git a/faq/bond-2.md b/faq/mainnet/bond-2.md similarity index 100% rename from faq/bond-2.md rename to faq/mainnet/bond-2.md diff --git a/faq/bond-3.md b/faq/mainnet/bond-3.md similarity index 100% rename from faq/bond-3.md rename to faq/mainnet/bond-3.md diff --git a/faq/mainnet/bond-4.md b/faq/mainnet/bond-4.md new file mode 100644 index 00000000..4991f37c --- /dev/null +++ b/faq/mainnet/bond-4.md @@ -0,0 +1,9 @@ +--- +title: Why add a bond? +--- + +Adding bond is a prevention measure to avoid Exit Request for your validators if they became unbonded. [Unbonded validators](https://docs.lido.fi/staking-modules/csm/guides/unbonded-validators) appear if the Node Operator's bond is no longer sufficient to cover all the validator keys uploaded to CSM by the Node Operator. + +If a [penalty](https://operatorportal.lido.fi/modules/community-staking-module#block-3951aa72ba1e471bafe95b40fef65d2b) was already applied, there is a relatively short period of time until the next VEBO report, which most likely will contain a validator Exit Request. During this period in between penalty application and the next VEBO report, Node Operators must top up bond to avoid Exit Requests for their validator(s). + +**Warning:** If the unbonded validator has already been requested to exit, Node Operators can only exit it. The bond top-up after the Exit Request will not reverse the Exit Request. diff --git a/faq/bond-5.md b/faq/mainnet/bond-5.md similarity index 100% rename from faq/bond-5.md rename to faq/mainnet/bond-5.md diff --git a/faq/keys-1.md b/faq/mainnet/keys-1.md similarity index 100% rename from faq/keys-1.md rename to faq/mainnet/keys-1.md diff --git a/faq/mainnet/keys-10.md b/faq/mainnet/keys-10.md new file mode 100644 index 00000000..978d3d64 --- /dev/null +++ b/faq/mainnet/keys-10.md @@ -0,0 +1,10 @@ +--- +title: When does a validator become withdrawn? +anchor: when-validator-become-withdrawn +--- + +On the Ethereum network, a validator can be withdrawn after successfully exiting from the consensus layer, but the exact timing of withdrawal depends on several factors related to Ethereum protocol mechanics: + +1. **Exit Queue**: When a validator initiates an exit, it enters an exit queue. The time required to exit depends on the number of validators exiting and the churn limit (the number of validators allowed to exit or enter per epoch). +2. **Withdrawal Process**: After exiting the active validator set, the validator enters a withdrawable state. This state is determined by the withdrawable epoch, which is set to the exit epoch + a minimum delay of 256 epochs (~27 hours). +3. **Finalization of Withdrawal**: Once the withdrawable epoch is reached, the validator balance will be transferred to the validator's withdrawal credentials (in the case of the Lido protocol, the Lido Withdrawal Vault) within the next iteration of the Consensus Layer withdrawal sweep cycle. How long this takes depends on the validator's position in the overall sweep cycle, and time difference between the withdrawable epoch and when its turn will come to be swept. Once the withdrawal has occurred, the fact of the withdrawal can be reported to CSM permissionlessly. Once that occurs, the part of the Node Operator’s bond used for this validator is released. At this point, the Node Operator can claim the bond on the Bond & Rewards Claim tab. diff --git a/faq/mainnet/keys-11.md b/faq/mainnet/keys-11.md new file mode 100644 index 00000000..88e7c56c --- /dev/null +++ b/faq/mainnet/keys-11.md @@ -0,0 +1,6 @@ +--- +title: What is a referrer? +onlyWithReferrer: true +--- + +A referrer is a software provider specializing in node/validator setup that integrated CSM into their tools. When a referrer directs solo stakers to join CSM via its tool, these Node Operators are marked as being referred from this provider. It doesn’t affect the Node Operators rewards in any way and is used just for the funnel-tracking purposes. diff --git a/faq/keys-12.md b/faq/mainnet/keys-12.md similarity index 100% rename from faq/keys-12.md rename to faq/mainnet/keys-12.md diff --git a/faq/keys-13.md b/faq/mainnet/keys-13.md similarity index 100% rename from faq/keys-13.md rename to faq/mainnet/keys-13.md diff --git a/faq/mainnet/keys-2.md b/faq/mainnet/keys-2.md new file mode 100644 index 00000000..fa478fde --- /dev/null +++ b/faq/mainnet/keys-2.md @@ -0,0 +1,11 @@ +--- +title: Why upload a bond? +--- + +Submitting a bond serves as a risk mitigation measure for both the Ethereum network and the Lido protocol. + +There are several major reasons for a CSM Node Operator's bond to be penalized, including: + +- **The validator has been slashed.** In this case, the initial (minimal) slashing penalty is confiscated. `Penalty amount` = `1 ETH (EFFECTIVE_BALANCE / 32)`; +- **The operator has stolen EL rewards (MEV)**. `Penalty amount` = `amount stolen` + `fixed stealing fine`; +- **The validator's withdrawal balance is less than 32 ETH**. `Penalty amount` = `32` - `validator's withdrawal balance.` diff --git a/faq/keys-3.md b/faq/mainnet/keys-3.md similarity index 100% rename from faq/keys-3.md rename to faq/mainnet/keys-3.md diff --git a/faq/keys-3a.md b/faq/mainnet/keys-3a.md similarity index 100% rename from faq/keys-3a.md rename to faq/mainnet/keys-3a.md diff --git a/faq/keys-4.md b/faq/mainnet/keys-4.md similarity index 100% rename from faq/keys-4.md rename to faq/mainnet/keys-4.md diff --git a/faq/keys-4a.md b/faq/mainnet/keys-4a.md similarity index 100% rename from faq/keys-4a.md rename to faq/mainnet/keys-4a.md diff --git a/faq/mainnet/keys-5.md b/faq/mainnet/keys-5.md new file mode 100644 index 00000000..8539e45a --- /dev/null +++ b/faq/mainnet/keys-5.md @@ -0,0 +1,5 @@ +--- +title: Difference between bond types (ETH, stETH, wstETH)? +--- + +Bonds are stored in the form of stETH to so that participation as a Node Operator is more capital efficient than if the bond were un-staked (or could only be staked if sufficient deposits to fill the submitted validators were present). While node operators can submit bond in ETH, stETH, or wstETH, any token other than stETH is converted to stETH for consistency in bond format. diff --git a/faq/keys-6.md b/faq/mainnet/keys-6.md similarity index 100% rename from faq/keys-6.md rename to faq/mainnet/keys-6.md diff --git a/faq/mainnet/keys-7.md b/faq/mainnet/keys-7.md new file mode 100644 index 00000000..2049a4e0 --- /dev/null +++ b/faq/mainnet/keys-7.md @@ -0,0 +1,5 @@ +--- +title: Why pay for key deletion? +--- + +Key deletion incurs a removal fee of 0.05 ETH, which is deducted from the Node Operator's bond per each deleted key to cover the maximal possible operational costs associated with the queue processing. This fee is intended to protect the module from potential DoS attacks by malicious actors who could clog the queue with empty slots by adding and removing keys, and covers the maximal possible operational costs associated with the queue processing. The fee discourages misuse, keeping the system clear of invalid keys or keys that don’t end up being deposited to. diff --git a/faq/mainnet/keys-8.md b/faq/mainnet/keys-8.md new file mode 100644 index 00000000..79c65c68 --- /dev/null +++ b/faq/mainnet/keys-8.md @@ -0,0 +1,5 @@ +--- +title: Can't see the key for deletion? +--- + +Only keys that have not been deposited yet can be deleted. If a key has already been deposited, the only way to retrieve the bond is [to exit the validator on the Consensus Layer (CL)](https://dvt-homestaker.stakesaurus.com/bonded-validators-setup/lido-csm/exiting-csm-validators). Once withdrawn, the node operator can claim the excess bond. diff --git a/faq/mainnet/keys-9.md b/faq/mainnet/keys-9.md new file mode 100644 index 00000000..c1361d38 --- /dev/null +++ b/faq/mainnet/keys-9.md @@ -0,0 +1,5 @@ +--- +title: How to stop validating in CSM? +--- + +Exiting CSM validator keys works the same way as exiting solo staking validator keys. The guide on how to exit the validator can be found [here](https://dvt-homestaker.stakesaurus.com/bonded-validators-setup/lido-csm/exiting-csm-validators#manual-exit). diff --git a/faq/mainnet/locked-1.md b/faq/mainnet/locked-1.md new file mode 100644 index 00000000..ea31fa37 --- /dev/null +++ b/faq/mainnet/locked-1.md @@ -0,0 +1,5 @@ +--- +title: Why is the bond locked? +--- + +Bond may be locked in the case of delayed penalties, typically for MEV stealing event reported by a dedicated committee. This measure ensures that node operators are held accountable for any misbehavior or rule violations. diff --git a/faq/mainnet/locked-2.md b/faq/mainnet/locked-2.md new file mode 100644 index 00000000..4aeaf545 --- /dev/null +++ b/faq/mainnet/locked-2.md @@ -0,0 +1,7 @@ +--- +title: How to unlock the bond? +--- + +To unlock the bond, the penalty amount, which includes the stolen amount and a fixed stealing fine, must be compensated on the "Locked bond" tab. This action can be performed from the Manager Address of the Node Operator. + +If there are disputes regarding the penalty, node operators can start a public discussion about the penalty applied on the Lido research forum under the [CSM Support](https://research.lido.fi/c/csm-support/21) category. diff --git a/faq/mainnet/locked-3.md b/faq/mainnet/locked-3.md new file mode 100644 index 00000000..d811e26f --- /dev/null +++ b/faq/mainnet/locked-3.md @@ -0,0 +1,5 @@ +--- +title: Consequences of not compensating? +--- + +In case of refusal to compensate the penalty, a protocol rule violation occurs which results in the reset of all node operator benefits. The locked bond is burned to compensate all stETH holders for the rewards stolen. diff --git a/faq/mainnet/main-1.md b/faq/mainnet/main-1.md new file mode 100644 index 00000000..d543ade1 --- /dev/null +++ b/faq/mainnet/main-1.md @@ -0,0 +1,9 @@ +--- +title: Why run an Ethereum validator? +--- + +Running an Ethereum validator allows one to: + +1. **Receive Staking Rewards**: Validators get network rewards for performing their duties on the Ethereum blockchain (note: incorrectly or not performing duties incurs penalties). +2. **Support the Network**: By running a validator, you actively contribute to the decentralization and security of the Ethereum network. Validators play a crucial role in reaching consensus and validating transactions, which enhances the network's reliability and resilience. +3. **Learn and Engage with the community**: Operating a validator node provides valuable insights into blockchain technology and consensus mechanisms. Through hands-on experience, individuals can deepen their understanding of Ethereum's inner workings. Moreover, it provides an opportunity to engage with the Ethereum community, share knowledge, and contribute to the network's development. diff --git a/faq/mainnet/main-2.md b/faq/mainnet/main-2.md new file mode 100644 index 00000000..21c6b4a8 --- /dev/null +++ b/faq/mainnet/main-2.md @@ -0,0 +1,10 @@ +--- +title: What is required to be a Node Operator in CSM? +--- + +Node operation in CSM involves a long-term commitment to Ethereum's decentralization. Responsibilities include: + +- **Hardware Setup**: Setting up a computer that meets the system requirements for running validator nodes. +- **Configuration**: Properly configuring nodes and validators, ensuring they are in sync with the Ethereum blockchain and other network participants. +- **Security Measures**: Implementing robust security measures to safeguard against external threats and internal vulnerabilities. +- **Maintenance**: Sustaining ongoing maintenance throughout the validators' lifespan, which involves monitoring performance, troubleshooting issues, and applying necessary updates. diff --git a/faq/mainnet/main-3.md b/faq/mainnet/main-3.md new file mode 100644 index 00000000..32d58fae --- /dev/null +++ b/faq/mainnet/main-3.md @@ -0,0 +1,8 @@ +--- +title: What do node operators receive in CSM? +--- + +Node operators benefit from: + +- **Daily Bond Rebase**: The collateral for CSM NOs is eligible for [rewards through stETH's rebase](https://help.lido.fi/en/articles/5230610-what-is-steth), even before validator activation. +- **Socialized Rewards**: Rewards are smoothed across the largest validator set, mitigating volatility. This means that even in the event of small outages or disruptions, node operators can still receive rewards, reducing the risk of rewards loss. diff --git a/faq/mainnet/main-4.md b/faq/mainnet/main-4.md new file mode 100644 index 00000000..72d4d148 --- /dev/null +++ b/faq/mainnet/main-4.md @@ -0,0 +1,12 @@ +--- +title: What are the risks of running a validator? +--- + +Node operators face several risks, including: + +1. **Technical Risk**: Maintaining reliable and secure hardware and software setups is essential. Any technical failure or vulnerability in the validator setup could lead to penalties. +2. **Penalties for Misbehavior**: Validators can be penalized for various reasons, such as going offline or attempting to manipulate the network. +3. **Market Risk**: The value of ETH can fluctuate significantly, impacting the value of the validators' staked ETH. +4. **Network Risk**: Validators are part of a decentralized network, and the security and stability of the Ethereum network as a whole can affect individual validators. Events such as network attacks or protocol upgrades may impact validator operations, leading to potential disruptions or losses. +5. **Operational Risk**: Validators require ongoing maintenance and monitoring to ensure smooth operation. Any operational issues, such as hardware failures or connectivity issues, could disrupt validator performance and result in rewards losses. +6. **Slashing Risk**: Validators can be slashed, meaning they lose a portion of their staked ETH, for violating network rules or behaving maliciously. Slashing can occur due to actions such as double signing or failing to validate correctly, resulting in significant penalties. diff --git a/faq/mainnet/main-5.md b/faq/mainnet/main-5.md new file mode 100644 index 00000000..68b4053b --- /dev/null +++ b/faq/mainnet/main-5.md @@ -0,0 +1,5 @@ +--- +title: How does CSM work? +--- + +Refer to [the CSM blog post](https://blog.lido.fi/lido-community-staking-an-overview/) for an overview, or [the CSM page](https://operatorportal.lido.fi/modules/community-staking-module) for a more detailed explanation of its mechanics and functionalities. diff --git a/faq/mainnet/main-6.md b/faq/mainnet/main-6.md new file mode 100644 index 00000000..e233beed --- /dev/null +++ b/faq/mainnet/main-6.md @@ -0,0 +1,11 @@ +--- +title: What makes CSM unique? +--- + +CSM provides several unique features to attract and benefit community stakers: + +- **Rewards Smoothing**: Rewards, including Execution Layer (EL) rewards and MEV, are smoothed with other modules to provide more stable rewards comparable to operating validators solo. +- **Low Bond Requirement**: A low bond for node operators makes participation more accessible to a broader range of prospective operators. +- **Exclusive Use of ETH (stETH)**: CSM exclusively uses ETH ((w)stETH) for bond and rewards, eliminating the need for other assets and simplifying the process for node operators. The bond can be submitted as ETH, wstETH, or stETH, and both bond and rewards can be withdrawn in any of the three forms (withdrawals in the form of ETH follow the [normal Lido on Ethereum unstaking process](https://help.lido.fi/en/articles/7858323-how-do-i-unstake-my-steth)). +- **Enhanced User Experience**: Accessible through a multitude of options -- from a web UI to integrations with Dappnode, Stereum, Eth-Docker, Sedge, Stereum, CoinPillar, etc., CSM offers a leading user-friendly experience, with reduced gas fees for on-chain operations and simplified transactions for joining and claiming rewards. +- **Higher Reward Potential**: Node operators are potentially able to earn more rewards compared to vanilla solo staking, making CSM an attractive option for operators looking to run more validators to earn rewards. diff --git a/faq/main-7.md b/faq/mainnet/main-7.md similarity index 100% rename from faq/main-7.md rename to faq/mainnet/main-7.md diff --git a/faq/main-7a.md b/faq/mainnet/main-7a.md similarity index 100% rename from faq/main-7a.md rename to faq/mainnet/main-7a.md diff --git a/faq/main-8.md b/faq/mainnet/main-8.md similarity index 100% rename from faq/main-8.md rename to faq/mainnet/main-8.md diff --git a/faq/mainnet/roles-1.md b/faq/mainnet/roles-1.md new file mode 100644 index 00000000..430183f7 --- /dev/null +++ b/faq/mainnet/roles-1.md @@ -0,0 +1,23 @@ +--- +title: What are rewards and Manager Addresses? +--- + +There are two addresses associated with your Node Operator that have different scope of responsibilities for managing your Node Operator. + +The Rewards Address is used for: + +- Claiming bond and rewards +- Adding extra bond amount +- Proposing a new Rewards Address +- Resetting the Manager Address to the current Rewards Address + +The Manager Address is used for: + +- Adding new keys +- Removing existing keys +- Adding extra bond amount +- Claiming bond and rewards to the Rewards Address +- Covering locked bond +- Proposing a new Manager Address + +Read more about addresses management [here](https://operatorportal.lido.fi/modules/community-staking-module#block-d3ad2b2bd3994a06b19dccc0794ac8d6). diff --git a/faq/mainnet/roles-2.md b/faq/mainnet/roles-2.md new file mode 100644 index 00000000..348752d1 --- /dev/null +++ b/faq/mainnet/roles-2.md @@ -0,0 +1,7 @@ +--- +title: Why should these addresses be different? +--- + +It's recommended to use different addresses for security reasons. For example, a hot wallet may be used for the Manager Address to simplify daily operations, while a cold wallet (or something like a Safe) is preferable for the Rewards Address to enhance security. + +Read more about addresses management [here](https://operatorportal.lido.fi/modules/community-staking-module#block-d3ad2b2bd3994a06b19dccc0794ac8d6). diff --git a/faq/mainnet/roles-3.md b/faq/mainnet/roles-3.md new file mode 100644 index 00000000..893ec291 --- /dev/null +++ b/faq/mainnet/roles-3.md @@ -0,0 +1,5 @@ +--- +title: How to accept a change in address request? +--- + +To accept a change in address request, connect to the CSM widget with the new address, navigate to the "Roles" → "Inbox requests" tab, select and accept the request, and confirm the transaction in your wallet. Changes are made once the transaction is processed. diff --git a/faq/mainnet/roles-4.md b/faq/mainnet/roles-4.md new file mode 100644 index 00000000..1734995e --- /dev/null +++ b/faq/mainnet/roles-4.md @@ -0,0 +1,5 @@ +--- +title: What to do if the change is submitted to a wrong address? +--- + +If a role change was submitted to the wrong address, the change can be revoked. For Rewards Address changes, navigate to "Roles" → "Rewards Address" → "Revoke". For Manager Address changes, go to "Roles" → "Manager Address" → "Revoke" diff --git a/faq/mainnet/roles-5.md b/faq/mainnet/roles-5.md new file mode 100644 index 00000000..90b335a5 --- /dev/null +++ b/faq/mainnet/roles-5.md @@ -0,0 +1,5 @@ +--- +title: What happens to rewards after changing the Rewards Address? +--- + +Rewards claimed to the previous Rewards Address remain there. After changing the Rewards Address, all rewards and excess bond accumulated on the bond balance can be claimed to the new Rewards Address. In the event of validator withdrawal, upon claiming of the bond, it would also be returned to the new Rewards Address. diff --git a/lib/getFaq.ts b/lib/getFaq.ts index c31c3a3e..ec8a3bb9 100644 --- a/lib/getFaq.ts +++ b/lib/getFaq.ts @@ -1,9 +1,10 @@ +/* eslint-disable no-console */ import matter from 'gray-matter'; import remark from 'remark'; import html from 'remark-html'; import externalLinks from 'remark-external-links'; import { getConfig } from 'config'; -import { CHAINS } from 'consts/chains'; +import { CHAINS } from '@lido-sdk/constants'; export type FaqItem = { id: string; @@ -16,8 +17,8 @@ export type FaqItem = { export type FaqGetter = () => Promise; -const readFaqFile = async (id: string): Promise => { - const fileContents = await import(`faq/${id}.md`); +const readFaqFile = async ([scope, id]: string[]): Promise => { + const fileContents = await import(`faq/${scope}/${id}.md`); const matterResult = matter(fileContents.default); const processedContent = await remark() @@ -36,13 +37,10 @@ const readFaqFile = async (id: string): Promise => { }; const { defaultChain } = getConfig(); -const isMainnet = defaultChain === CHAINS.Mainnet; +const chainName = CHAINS[defaultChain].toLowerCase(); export const readFaqFiles = async (fileNames: string[]) => { - const ids = isMainnet - ? fileNames - : fileNames.map((name) => `testnet-${name}`); - + const ids = fileNames.map((name) => [chainName, name]); return Promise.all(ids.map(readFaqFile)); }; From efd38c1ef4d8d1f48393ed4942b8ceab38582075 Mon Sep 17 00:00:00 2001 From: exromany Date: Tue, 18 Mar 2025 16:45:22 +0300 Subject: [PATCH 12/58] chore: blockscout explorer --- .../etherscan-address-link.tsx | 31 +++++++++---------- .../tx-link-etherscan/tx-link-etherscan.tsx | 13 +++++--- 2 files changed, 23 insertions(+), 21 deletions(-) diff --git a/shared/components/external-icon-link/etherscan-address-link.tsx b/shared/components/external-icon-link/etherscan-address-link.tsx index e5d68dee..87665d93 100644 --- a/shared/components/external-icon-link/etherscan-address-link.tsx +++ b/shared/components/external-icon-link/etherscan-address-link.tsx @@ -10,26 +10,25 @@ type Props = { address: string; }; -export const EtherscanAddressLink: FC = ({ address }) => { - const { chainId } = useAccount(); - +const getExplorerAddressLink = (chainId: number, address: string) => { if (chainId === CHAINS.Hoodi) { - return null; + return `https://hoodi.cloud.blockscout.com/address/${address}`; } + return getEtherscanAddressLink(chainId, address); +}; + +export const EtherscanAddressLink: FC = ({ address }) => { + const { chainId } = useAccount(); - const href = getEtherscanAddressLink(chainId ?? 0, address); + if (!address) return null; return ( - <> - {href && ( - - - - )} - + + + ); }; diff --git a/shared/components/tx-link-etherscan/tx-link-etherscan.tsx b/shared/components/tx-link-etherscan/tx-link-etherscan.tsx index 354eb9c0..4f1a21cb 100644 --- a/shared/components/tx-link-etherscan/tx-link-etherscan.tsx +++ b/shared/components/tx-link-etherscan/tx-link-etherscan.tsx @@ -10,6 +10,13 @@ type TxLinkEtherscanProps = { txHash?: string | null; }; +const getExplorerTxLink = (chainId: number, hash: string) => { + if (chainId === CHAINS.Hoodi) { + return `https://hoodi.cloud.blockscout.com/tx/${hash}`; + } + return getEtherscanTxLink(chainId, hash); +}; + export const TxLinkEtherscan: FC = ({ txHash, text = 'View on Etherscan', @@ -18,13 +25,9 @@ export const TxLinkEtherscan: FC = ({ if (!txHash) return null; - if (chainId === CHAINS.Hoodi) { - return null; - } - return ( {text} From c9a611a7140b74cb24628e3c3e690abdb7fca7e1 Mon Sep 17 00:00:00 2001 From: exromany Date: Tue, 18 Mar 2025 17:01:05 +0300 Subject: [PATCH 13/58] fix: hoodi CL api --- config/rpc/cl.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/config/rpc/cl.ts b/config/rpc/cl.ts index 140c4fe9..14f0e210 100644 --- a/config/rpc/cl.ts +++ b/config/rpc/cl.ts @@ -15,7 +15,6 @@ import { config } from '../get-config'; import { useUserConfig } from '../user-config'; export const getBackendApiPath = (chainId: string | number): string => { - if (chainId === CHAINS.Hoodi) return ''; const BASE_URL = typeof window === 'undefined' ? '' : window.location.origin; return `${BASE_URL}/${API_ROUTES.CL}/${chainId}`; }; From 6acaefbdefe59cb165fa8a1d31b4f3f17ae4231f Mon Sep 17 00:00:00 2001 From: exromany Date: Tue, 18 Mar 2025 18:12:05 +0300 Subject: [PATCH 14/58] fix: deposit queue --- .../view-keys/deposit-queue/use-deposit-queue-graph.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/features/view-keys/deposit-queue/use-deposit-queue-graph.ts b/features/view-keys/deposit-queue/use-deposit-queue-graph.ts index 9f36373d..d180d644 100644 --- a/features/view-keys/deposit-queue/use-deposit-queue-graph.ts +++ b/features/view-keys/deposit-queue/use-deposit-queue-graph.ts @@ -7,7 +7,7 @@ import { DepositDataInputType } from 'shared/hook-form/form-controller'; import { useCSMShareLimitInfo, useNodeOperatorInfo } from 'shared/hooks'; import { useCSMQueueBatches } from 'shared/hooks/useCSMQueueBatches'; -const POTENTIAL_ADDED = BigNumber.from(100); +const POTENTIAL_ADDED = BigNumber.from(25); const BACK = BigNumber.from(30); type Pos = { size: number; offset: number }; @@ -41,7 +41,7 @@ export const useDepositQueueGraph = (fullView = false) => { const isSubmitting = submitting !== undefined; const potential = added.lt(POTENTIAL_ADDED) ? POTENTIAL_ADDED : added; - const m0 = active.isZero() ? queue : active; + const m0 = active.lt(BACK) ? Zero : active; const m1 = m0.sub(BACK).isNegative() ? m0 : m0.sub(BACK); const m2 = active.add(queue).add(potential); const md = m2.sub(m1); @@ -70,7 +70,11 @@ export const useDepositQueueGraph = (fullView = false) => { return value.isZero() ? 0 : Math.max(minSize, cc(value.add(prev)) - p); }; - const queueUnderLimit = queue.lt(activeLeft) ? queue : activeLeft; + const queueUnderLimit = activeLeft.gt(Zero) + ? queue.lt(activeLeft) + ? queue + : activeLeft + : Zero; const queueOverLimit = queue.sub(queueUnderLimit); const activeSize = ccc(active); From 3d170bcb2bff55180f8c869ce779a129e1f18529 Mon Sep 17 00:00:00 2001 From: exromany Date: Wed, 19 Mar 2025 15:52:01 +0300 Subject: [PATCH 15/58] fix: key in activation pending status comment --- shared/components/status-comment/comments.tsx | 14 ++++++++++++++ .../components/status-comment/status-comment.tsx | 10 +++++----- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/shared/components/status-comment/comments.tsx b/shared/components/status-comment/comments.tsx index 1429aeb5..b725cc60 100644 --- a/shared/components/status-comment/comments.tsx +++ b/shared/components/status-comment/comments.tsx @@ -68,6 +68,20 @@ export const CommentExiting: FC = () => ( ); +export const CommentActivationPending: FC = () => { + return ( + + When does the validator become active? + + ); +}; + export const CommentDepositable: FC = () => { const { data } = useCSMShareLimitInfo(); diff --git a/shared/components/status-comment/status-comment.tsx b/shared/components/status-comment/status-comment.tsx index 8e9705f7..5c5bbb39 100644 --- a/shared/components/status-comment/status-comment.tsx +++ b/shared/components/status-comment/status-comment.tsx @@ -1,6 +1,7 @@ import { FC } from 'react'; import { KEY_STATUS } from 'consts/key-status'; import { + CommentActivationPending, CommentDepositable, CommentExiting, CommentExitRequested, @@ -48,11 +49,10 @@ export const StatusComment: FC<{ statuses: KEY_STATUS[] }> = ({ statuses }) => { ) return ; - if ( - statuses.includes(KEY_STATUS.DEPOSITABLE) || - statuses.includes(KEY_STATUS.ACTIVATION_PENDING) - ) - return ; + if (statuses.includes(KEY_STATUS.ACTIVATION_PENDING)) + return ; + + if (statuses.includes(KEY_STATUS.DEPOSITABLE)) return ; return null; }; From ceaeb318cb544fd718a8a60470bbe84768b44ed5 Mon Sep 17 00:00:00 2001 From: exromany Date: Wed, 19 Mar 2025 15:52:22 +0300 Subject: [PATCH 16/58] chore: disable external dashboards for hoodi --- features/dashboard/dashboard.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/features/dashboard/dashboard.tsx b/features/dashboard/dashboard.tsx index 411a5d94..ee7910f8 100644 --- a/features/dashboard/dashboard.tsx +++ b/features/dashboard/dashboard.tsx @@ -3,6 +3,10 @@ import { BondSection } from './bond'; import { KeysSection } from './keys'; import { RolesSection } from './roles'; import { ExternalSection } from './external'; +import { getConfig } from 'config'; +import { CHAINS } from 'consts/chains'; + +const { defaultChain } = getConfig(); export const Dashboard: FC = () => { return ( @@ -10,7 +14,7 @@ export const Dashboard: FC = () => { - + {defaultChain !== CHAINS.Hoodi && } ); }; From 9a518c3e834a6bf8cb1b1778fd34b62a374c76a9 Mon Sep 17 00:00:00 2001 From: exromany Date: Wed, 19 Mar 2025 17:37:22 +0300 Subject: [PATCH 17/58] chore: links --- consts/external-links.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/consts/external-links.ts b/consts/external-links.ts index 6316369e..e874c728 100644 --- a/consts/external-links.ts +++ b/consts/external-links.ts @@ -85,15 +85,13 @@ export const EXTERNAL_LINKS_BY_NETWORK: Record = [CHAINS.Hoodi]: { earlyAdoptionTree: '', rewardsTree: '', - earlyAdoptionSources: - 'https://github.com/lidofinance/community-staking-module/blob/v1.0/artifacts/holesky/early-adoption/addresses.json', - earlyAdoptionAbout: - 'https://operatorportal.lido.fi/modules/community-staking-module#block-ef60a1fa96ae4c7995dd7794de2a3e22', + earlyAdoptionSources: '', + earlyAdoptionAbout: '', feedbackForm: 'https://forms.gle/ZBUqbykaZokJLf4M7', - stakeWidget: 'https://stake-holesky.testnet.fi', + stakeWidget: 'https://stake-hoodi.testnet.fi', - feesMonitoring: 'https://fees-monitoring-holesky.testnet.fi', - operatorsWidget: 'https://operators-holesky.testnet.fi', + feesMonitoring: 'https://fees-monitoring-hoodi.testnet.fi', + operatorsWidget: 'https://operators-hoodi.testnet.fi', beaconchain: 'https://holesky.beaconcha.in', beaconchainDashboard: 'https://v2-beta-holesky.beaconcha.in/dashboard', ratedExplorer: 'https://explorer.rated.network', From bc6d3d875e0f8b3d71e215e9e915ad3516cc1fae Mon Sep 17 00:00:00 2001 From: exromany Date: Wed, 19 Mar 2025 18:09:51 +0300 Subject: [PATCH 18/58] chore: faq hoodi with correct curve image --- faq/hoodi/keys-3.md | 8 ++++---- faq/hoodi/keys-3a.md | 8 ++++---- faq/hoodi/keys-4.md | 4 ++-- faq/hoodi/keys-4a.md | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/faq/hoodi/keys-3.md b/faq/hoodi/keys-3.md index a4c6e0db..dd1013fa 100644 --- a/faq/hoodi/keys-3.md +++ b/faq/hoodi/keys-3.md @@ -4,10 +4,10 @@ earlyAdoption: false anchor: how-bond-is-calculated --- -The initial bond requirement for the first validator for the testnet is 2 stETH. However, for [Early Adopters](https://operatorportal.lido.fi/modules/community-staking-module#block-ef60a1fa96ae4c7995dd7794de2a3e22), this amount is reduced to 1.5 stETH to incentivize early participation. +The initial bond requirement for the first validator for the Hoodi testnet is 2.4 stETH. However, for [Early Adopters](https://operatorportal.lido.fi/modules/community-staking-module#block-ef60a1fa96ae4c7995dd7794de2a3e22), this amount is reduced to 1.5 stETH to incentivize early participation. -Subsequent bond amounts depend on the total number of validators operated by the node operator and follow a specific function known as the “[bond curve](https://operatorportal.lido.fi/modules/community-staking-module#block-2d1c307d95fc4f8ab7c32b7584f795cf)”, which adjusts the bond requirement based on the operator's validator count. +The amount for the second and subsequent validators is 1.3 stETH -For the testnet, the values for the bond curve are the following: +For the Hoodi testnet, the values for the bond curve are the following: -![curve.png](/assets/curve-common.png) +![curve.png](/assets/mainnet-curve-common.png) diff --git a/faq/hoodi/keys-3a.md b/faq/hoodi/keys-3a.md index f1ad7a06..5c7af749 100644 --- a/faq/hoodi/keys-3a.md +++ b/faq/hoodi/keys-3a.md @@ -4,10 +4,10 @@ earlyAdoption: true anchor: how-bond-is-calculated --- -The initial bond requirement for the first validator for the testnet is 2 stETH. However, for [Early Adopters](https://operatorportal.lido.fi/modules/community-staking-module#block-ef60a1fa96ae4c7995dd7794de2a3e22), this amount is reduced to 1.5 stETH to incentivize early participation. +The initial bond requirement for the first validator for the Hoodi testnet is 2.4 stETH. However, for [Early Adopters](https://operatorportal.lido.fi/modules/community-staking-module#block-ef60a1fa96ae4c7995dd7794de2a3e22), this amount is reduced to 1.5 stETH to incentivize early participation. -Subsequent bond amounts depend on the total number of validators operated by the node operator and follow a specific function known as the “[bond curve](https://operatorportal.lido.fi/modules/community-staking-module#block-2d1c307d95fc4f8ab7c32b7584f795cf)”, which adjusts the bond requirement based on the operator's validator count. +The amount for the second and subsequent validators is 1.3 stETH -For the testnet, the values for the bond curve are the following: +For the Hoodi testnet, the values for the bond curve are the following: -![curve.png](/assets/curve-ea.png) +![curve.png](/assets/mainnet-curve-ea.png) diff --git a/faq/hoodi/keys-4.md b/faq/hoodi/keys-4.md index 731c6ded..663b9fd4 100644 --- a/faq/hoodi/keys-4.md +++ b/faq/hoodi/keys-4.md @@ -5,6 +5,6 @@ earlyAdoption: false [The bond curve](https://operatorportal.lido.fi/modules/community-staking-module#block-2d1c307d95fc4f8ab7c32b7584f795cf) is a function that determines the amount of bond required for each subsequent validator operated by the node operator. For [Early Adopters](https://operatorportal.lido.fi/modules/community-staking-module#block-ef60a1fa96ae4c7995dd7794de2a3e22), a unique bond curve function is applied to incentivize early participation. -For the testnet, the values for the bond curve are the following: +For the Hoodi testnet, the values for the bond curve are the following: -![curve.png](/assets/curve-common.png) +![curve.png](/assets/mainnet-curve-common.png) diff --git a/faq/hoodi/keys-4a.md b/faq/hoodi/keys-4a.md index 5b43be9b..810697a1 100644 --- a/faq/hoodi/keys-4a.md +++ b/faq/hoodi/keys-4a.md @@ -5,6 +5,6 @@ earlyAdoption: true [The bond curve](https://operatorportal.lido.fi/modules/community-staking-module#block-2d1c307d95fc4f8ab7c32b7584f795cf) is a function that determines the amount of bond required for each subsequent validator operated by the node operator. For [Early Adopters](https://operatorportal.lido.fi/modules/community-staking-module#block-ef60a1fa96ae4c7995dd7794de2a3e22), a unique bond curve function is applied to incentivize early participation. -For the testnet, the values for the bond curve are the following: +For the Hoodi testnet, the values for the bond curve are the following: -![curve.png](/assets/curve-ea.png) +![curve.png](/assets/mainnet-curve-ea.png) From d941be3c4e8952d319ec976afe43a5e6ab281f07 Mon Sep 17 00:00:00 2001 From: exromany Date: Wed, 19 Mar 2025 19:20:14 +0300 Subject: [PATCH 19/58] fix: exitbus contract address --- consts/csm-constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/consts/csm-constants.ts b/consts/csm-constants.ts index 0fe6f66b..0bf66769 100644 --- a/consts/csm-constants.ts +++ b/consts/csm-constants.ts @@ -51,7 +51,7 @@ export const CONSTANTS_BY_NETWORK: Record = { CSFeeOracle: '0xe7314f561B2e72f9543F1004e741bab6Fc51028B', CSModule: '0x79CEf36D84743222f37765204Bec41E92a93E59d', CSVerifier: '0x16D0f6068D211608e3703323314aa976a6492D09', - ExitBusOracle: '0x30308CD8844fb2DB3ec4D056F1d475a802DCA07c', + ExitBusOracle: '0x8664d394C2B3278F26A1B44B967aEf99707eeAB2', StakingRouter: '0xCc820558B39ee15C7C45B59390B503b83fb499A8', }, deploymentBlockNumber: undefined, From afccf200dd9b1db9a60ca0b41fe4a1d0ace5b0e4 Mon Sep 17 00:00:00 2001 From: exromany Date: Fri, 21 Mar 2025 13:34:22 +0300 Subject: [PATCH 20/58] chore: hoodi rawards frame = 1d --- consts/csm-constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/consts/csm-constants.ts b/consts/csm-constants.ts index 0bf66769..d9955e6c 100644 --- a/consts/csm-constants.ts +++ b/consts/csm-constants.ts @@ -58,7 +58,7 @@ export const CONSTANTS_BY_NETWORK: Record = { stakingModuleId: 4, withdrawalCredentials: '0x4473dCDDbf77679A643BdB654dbd86D67F8d32f2', retentionPeriodMins: 80_640, // 8 weeks - slotsPerFrame: 32 * 225 * 7, // 7 days + slotsPerFrame: 32 * 225 * 1, // 1 days }, [CHAINS.Holesky]: { contracts: { From 66ed691a0994562e9d2ac0e95b7adaa09d84af73 Mon Sep 17 00:00:00 2001 From: exromany Date: Mon, 24 Mar 2025 16:54:53 +0700 Subject: [PATCH 21/58] chore: refactor rewards frame dates --- abi/HashConsensus.json | 1141 +++++++++++++++++ consts/csm-constants.ts | 4 + .../dashboard/bond/available-to-claim.tsx | 9 +- features/dashboard/bond/last-rewards.tsx | 31 +- shared/hooks/index.ts | 3 +- shared/hooks/useCsmContracts.ts | 8 + ...ewardsFrame.ts => useLastRewardsReport.ts} | 38 - shared/hooks/useRewardsFrame.ts | 62 + utils/format-date.ts | 7 +- utilsApi/contractAddressesMetricsMap.ts | 8 + 10 files changed, 1242 insertions(+), 69 deletions(-) create mode 100644 abi/HashConsensus.json rename shared/hooks/{useLastRewardsFrame.ts => useLastRewardsReport.ts} (78%) create mode 100644 shared/hooks/useRewardsFrame.ts diff --git a/abi/HashConsensus.json b/abi/HashConsensus.json new file mode 100644 index 00000000..00f5a7a8 --- /dev/null +++ b/abi/HashConsensus.json @@ -0,0 +1,1141 @@ +[ + { + "type": "constructor", + "inputs": [ + { + "name": "slotsPerEpoch", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "secondsPerSlot", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "genesisTime", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "epochsPerFrame", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "fastLaneLengthSlots", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "admin", + "type": "address", + "internalType": "address" + }, + { + "name": "reportProcessor", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "DEFAULT_ADMIN_ROLE", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "DISABLE_CONSENSUS_ROLE", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "MANAGE_FAST_LANE_CONFIG_ROLE", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "MANAGE_FRAME_CONFIG_ROLE", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "MANAGE_MEMBERS_AND_QUORUM_ROLE", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "MANAGE_REPORT_PROCESSOR_ROLE", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "addMember", + "inputs": [ + { + "name": "addr", + "type": "address", + "internalType": "address" + }, + { + "name": "quorum", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "disableConsensus", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "getChainConfig", + "inputs": [], + "outputs": [ + { + "name": "slotsPerEpoch", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "secondsPerSlot", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "genesisTime", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getConsensusState", + "inputs": [], + "outputs": [ + { + "name": "refSlot", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "consensusReport", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "isReportProcessing", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getConsensusStateForMember", + "inputs": [ + { + "name": "addr", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "result", + "type": "tuple", + "internalType": "struct HashConsensus.MemberConsensusState", + "components": [ + { + "name": "currentFrameRefSlot", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "currentFrameConsensusReport", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "isMember", + "type": "bool", + "internalType": "bool" + }, + { + "name": "isFastLane", + "type": "bool", + "internalType": "bool" + }, + { + "name": "canReport", + "type": "bool", + "internalType": "bool" + }, + { + "name": "lastMemberReportRefSlot", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "currentFrameMemberReport", + "type": "bytes32", + "internalType": "bytes32" + } + ] + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getCurrentFrame", + "inputs": [], + "outputs": [ + { + "name": "refSlot", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "reportProcessingDeadlineSlot", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getFastLaneMembers", + "inputs": [], + "outputs": [ + { + "name": "addresses", + "type": "address[]", + "internalType": "address[]" + }, + { + "name": "lastReportedRefSlots", + "type": "uint256[]", + "internalType": "uint256[]" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getFrameConfig", + "inputs": [], + "outputs": [ + { + "name": "initialEpoch", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "epochsPerFrame", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "fastLaneLengthSlots", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getInitialRefSlot", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getIsFastLaneMember", + "inputs": [ + { + "name": "addr", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getIsMember", + "inputs": [ + { + "name": "addr", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getMembers", + "inputs": [], + "outputs": [ + { + "name": "addresses", + "type": "address[]", + "internalType": "address[]" + }, + { + "name": "lastReportedRefSlots", + "type": "uint256[]", + "internalType": "uint256[]" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getQuorum", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getReportProcessor", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getReportVariants", + "inputs": [], + "outputs": [ + { + "name": "variants", + "type": "bytes32[]", + "internalType": "bytes32[]" + }, + { + "name": "support", + "type": "uint256[]", + "internalType": "uint256[]" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getRoleAdmin", + "inputs": [ + { + "name": "role", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getRoleMember", + "inputs": [ + { + "name": "role", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "index", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getRoleMemberCount", + "inputs": [ + { + "name": "role", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "grantRole", + "inputs": [ + { + "name": "role", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "account", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "hasRole", + "inputs": [ + { + "name": "role", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "account", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "removeMember", + "inputs": [ + { + "name": "addr", + "type": "address", + "internalType": "address" + }, + { + "name": "quorum", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "renounceRole", + "inputs": [ + { + "name": "role", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "callerConfirmation", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "revokeRole", + "inputs": [ + { + "name": "role", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "account", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "setFastLaneLengthSlots", + "inputs": [ + { + "name": "fastLaneLengthSlots", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "setFrameConfig", + "inputs": [ + { + "name": "epochsPerFrame", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "fastLaneLengthSlots", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "setQuorum", + "inputs": [ + { + "name": "quorum", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "setReportProcessor", + "inputs": [ + { + "name": "newProcessor", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "submitReport", + "inputs": [ + { + "name": "slot", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "report", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "consensusVersion", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "supportsInterface", + "inputs": [ + { + "name": "interfaceId", + "type": "bytes4", + "internalType": "bytes4" + } + ], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "updateInitialEpoch", + "inputs": [ + { + "name": "initialEpoch", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "event", + "name": "ConsensusLost", + "inputs": [ + { + "name": "refSlot", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "ConsensusReached", + "inputs": [ + { + "name": "refSlot", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + }, + { + "name": "report", + "type": "bytes32", + "indexed": false, + "internalType": "bytes32" + }, + { + "name": "support", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "FastLaneConfigSet", + "inputs": [ + { + "name": "fastLaneLengthSlots", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "FrameConfigSet", + "inputs": [ + { + "name": "newInitialEpoch", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "newEpochsPerFrame", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Initialized", + "inputs": [ + { + "name": "version", + "type": "uint64", + "indexed": false, + "internalType": "uint64" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "MemberAdded", + "inputs": [ + { + "name": "addr", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "newTotalMembers", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "newQuorum", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "MemberRemoved", + "inputs": [ + { + "name": "addr", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "newTotalMembers", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "newQuorum", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "QuorumSet", + "inputs": [ + { + "name": "newQuorum", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "totalMembers", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "prevQuorum", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "ReportProcessorSet", + "inputs": [ + { + "name": "processor", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "prevProcessor", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "ReportReceived", + "inputs": [ + { + "name": "refSlot", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + }, + { + "name": "member", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "report", + "type": "bytes32", + "indexed": false, + "internalType": "bytes32" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "RoleAdminChanged", + "inputs": [ + { + "name": "role", + "type": "bytes32", + "indexed": true, + "internalType": "bytes32" + }, + { + "name": "previousAdminRole", + "type": "bytes32", + "indexed": true, + "internalType": "bytes32" + }, + { + "name": "newAdminRole", + "type": "bytes32", + "indexed": true, + "internalType": "bytes32" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "RoleGranted", + "inputs": [ + { + "name": "role", + "type": "bytes32", + "indexed": true, + "internalType": "bytes32" + }, + { + "name": "account", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "sender", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "RoleRevoked", + "inputs": [ + { + "name": "role", + "type": "bytes32", + "indexed": true, + "internalType": "bytes32" + }, + { + "name": "account", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "sender", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "error", + "name": "AccessControlBadConfirmation", + "inputs": [] + }, + { + "type": "error", + "name": "AccessControlUnauthorizedAccount", + "inputs": [ + { + "name": "account", + "type": "address", + "internalType": "address" + }, + { + "name": "neededRole", + "type": "bytes32", + "internalType": "bytes32" + } + ] + }, + { + "type": "error", + "name": "AddressCannotBeZero", + "inputs": [] + }, + { + "type": "error", + "name": "AdminCannotBeZero", + "inputs": [] + }, + { + "type": "error", + "name": "ConsensusReportAlreadyProcessing", + "inputs": [] + }, + { + "type": "error", + "name": "DuplicateMember", + "inputs": [] + }, + { + "type": "error", + "name": "DuplicateReport", + "inputs": [] + }, + { + "type": "error", + "name": "EmptyReport", + "inputs": [] + }, + { + "type": "error", + "name": "EpochsPerFrameCannotBeZero", + "inputs": [] + }, + { + "type": "error", + "name": "FastLanePeriodCannotBeLongerThanFrame", + "inputs": [] + }, + { + "type": "error", + "name": "InitialEpochAlreadyArrived", + "inputs": [] + }, + { + "type": "error", + "name": "InitialEpochIsYetToArrive", + "inputs": [] + }, + { + "type": "error", + "name": "InitialEpochRefSlotCannotBeEarlierThanProcessingSlot", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidChainConfig", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidInitialization", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidSlot", + "inputs": [] + }, + { + "type": "error", + "name": "NewProcessorCannotBeTheSame", + "inputs": [] + }, + { + "type": "error", + "name": "NonFastLaneMemberCannotReportWithinFastLaneInterval", + "inputs": [] + }, + { + "type": "error", + "name": "NonMember", + "inputs": [] + }, + { + "type": "error", + "name": "NotInitializing", + "inputs": [] + }, + { + "type": "error", + "name": "NumericOverflow", + "inputs": [] + }, + { + "type": "error", + "name": "QuorumTooSmall", + "inputs": [ + { + "name": "minQuorum", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "receivedQuorum", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "ReportProcessorCannotBeZero", + "inputs": [] + }, + { + "type": "error", + "name": "SafeCastOverflowedUintDowncast", + "inputs": [ + { + "name": "bits", + "type": "uint8", + "internalType": "uint8" + }, + { + "name": "value", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "StaleReport", + "inputs": [] + }, + { + "type": "error", + "name": "UnexpectedConsensusVersion", + "inputs": [ + { + "name": "expected", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "received", + "type": "uint256", + "internalType": "uint256" + } + ] + } +] diff --git a/consts/csm-constants.ts b/consts/csm-constants.ts index d9955e6c..54b7145b 100644 --- a/consts/csm-constants.ts +++ b/consts/csm-constants.ts @@ -13,6 +13,7 @@ type CsmContract = | 'CSFeeOracle' | 'CSModule' | 'CSVerifier' + | 'HashConsensus' | 'ExitBusOracle' | 'StakingRouter'; @@ -34,6 +35,7 @@ export const CONSTANTS_BY_NETWORK: Record = { CSFeeOracle: '0x4D4074628678Bd302921c20573EEa1ed38DdF7FB', CSModule: '0xdA7dE2ECdDfccC6c3AF10108Db212ACBBf9EA83F', CSVerifier: '0x3Dfc50f22aCA652a0a6F28a0F892ab62074b5583', + HashConsensus: '0x71093efF8D8599b5fA340D665Ad60fA7C80688e4', ExitBusOracle: '0x0De4Ea0184c2ad0BacA7183356Aea5B8d5Bf5c6e', StakingRouter: '0xFdDf38947aFB03C621C71b06C9C70bce73f12999', }, @@ -51,6 +53,7 @@ export const CONSTANTS_BY_NETWORK: Record = { CSFeeOracle: '0xe7314f561B2e72f9543F1004e741bab6Fc51028B', CSModule: '0x79CEf36D84743222f37765204Bec41E92a93E59d', CSVerifier: '0x16D0f6068D211608e3703323314aa976a6492D09', + HashConsensus: '0x54f74a10e4397dDeF85C4854d9dfcA129D72C637', ExitBusOracle: '0x8664d394C2B3278F26A1B44B967aEf99707eeAB2', StakingRouter: '0xCc820558B39ee15C7C45B59390B503b83fb499A8', }, @@ -68,6 +71,7 @@ export const CONSTANTS_BY_NETWORK: Record = { CSFeeOracle: '0xaF57326C7d513085051b50912D51809ECC5d98Ee', CSModule: '0x4562c3e63c2e586cD1651B958C22F88135aCAd4f', CSVerifier: '0x6DcA479178E6Ae41CCEB72a88FfDaa3e10E83CB7', + HashConsensus: '0xbF38618Ea09B503c1dED867156A0ea276Ca1AE37', ExitBusOracle: '0xffDDF7025410412deaa05E3E1cE68FE53208afcb', StakingRouter: '0xd6EbF043D30A7fe46D1Db32BA90a0A51207FE229', }, diff --git a/features/dashboard/bond/available-to-claim.tsx b/features/dashboard/bond/available-to-claim.tsx index f6beb05d..fd80c9c2 100644 --- a/features/dashboard/bond/available-to-claim.tsx +++ b/features/dashboard/bond/available-to-claim.tsx @@ -4,10 +4,9 @@ import { useNodeOperatorId } from 'providers/node-operator-provider'; import { FC } from 'react'; import { Counter, IconTooltip } from 'shared/components'; import { - getNextRewardsFrame, - useLastRewardsSlot, useNodeOperatorBalance, useNodeOperatorRewards, + useRewardsFrame, } from 'shared/hooks'; import { useAvailableToClaim } from 'shared/hooks/useAvailableToClaim'; import { formatDate } from 'utils'; @@ -23,10 +22,8 @@ export const AvailableToClaim: FC = () => { const { data: rewards, initialLoading: isRewardsLoading } = useNodeOperatorRewards(id); - const { data: rewardsSlot } = useLastRewardsSlot(); - const nextRewardsDate = rewardsSlot?.timestamp - ? formatDate(getNextRewardsFrame(rewardsSlot.timestamp)) - : null; + const { data: rewardsFrame } = useRewardsFrame(); + const nextRewardsDate = formatDate(rewardsFrame?.nextRewards); const availableToClaim = useAvailableToClaim({ bond, diff --git a/features/dashboard/bond/last-rewards.tsx b/features/dashboard/bond/last-rewards.tsx index ca4d35ea..f57fd425 100644 --- a/features/dashboard/bond/last-rewards.tsx +++ b/features/dashboard/bond/last-rewards.tsx @@ -6,21 +6,18 @@ import { Text, Tooltip, } from '@lidofinance/lido-ui'; -import { differenceInCalendarDays, fromUnixTime } from 'date-fns'; import { ModalComponentType, useModal } from 'providers/modal-provider'; import { useNodeOperatorId } from 'providers/node-operator-provider'; import { FC, useCallback } from 'react'; import { GrayText, Stack, TextBlock, TxLinkEtherscan } from 'shared/components'; import { FaqElement } from 'shared/components/faq/styles'; import { - getNextRewardsFrame, - getPrevRewardsFrame, useLastOperatorRewards, - useLastRewardsSlot, useLastRewrdsTx, useNodeOperatorInfo, + useRewardsFrame, } from 'shared/hooks'; -import { formatDate, formatPercent } from 'utils'; +import { countDaysLeft, formatDate, formatPercent } from 'utils'; import { Balance } from './balance'; import { AccordionStyle, @@ -38,24 +35,12 @@ export const LastRewards: FC = () => { const id = useNodeOperatorId(); const { data: info } = useNodeOperatorInfo(id); - const { data: rewardsSlot } = useLastRewardsSlot(); + const { data: rewardsFrame } = useRewardsFrame(); - const lastRewardsDate = rewardsSlot?.timestamp - ? formatDate(rewardsSlot.timestamp) - : null; - const prevRewardsDate = rewardsSlot?.timestamp - ? formatDate(getPrevRewardsFrame(rewardsSlot.timestamp)) - : null; - const nextRewardsDate = rewardsSlot?.timestamp - ? formatDate(getNextRewardsFrame(rewardsSlot.timestamp)) - : null; - - const daysLeft = rewardsSlot?.timestamp - ? differenceInCalendarDays( - fromUnixTime(getNextRewardsFrame(rewardsSlot.timestamp)), - new Date(), - ) - : null; + const lastRewardsDate = formatDate(rewardsFrame?.lastRewards); + const prevRewardsDate = formatDate(rewardsFrame?.prevRewards); + const nextRewardsDate = formatDate(rewardsFrame?.nextRewards); + const daysLeft = countDaysLeft(rewardsFrame?.nextRewards); const showThisSection = lastRewards || (info?.totalDepositedKeys ?? 0) > 0; @@ -92,7 +77,7 @@ export const LastRewards: FC = () => { Next rewards distribution - {rewardsSlot?.timestamp ? ( + {lastRewardsDate && nextRewardsDate ? ( Report frame: {lastRewardsDate} — {nextRewardsDate} diff --git a/shared/hooks/index.ts b/shared/hooks/index.ts index b94a509a..cc9a1183 100644 --- a/shared/hooks/index.ts +++ b/shared/hooks/index.ts @@ -60,7 +60,7 @@ export * from './useIsContract'; export * from './useIsMultisig'; export * from './useIsReportStealingRole'; export * from './useKeysCache'; -export * from './useLastRewardsFrame'; +export * from './useLastRewardsReport'; export * from './useMaxGasPrice'; export * from './useMergeSwr'; export * from './useNodeOperatorBalance'; @@ -76,6 +76,7 @@ export * from './useNodeOperatorRewards'; export * from './useNodeOperatorSummary'; export * from './useNodeOperatorUnbondedKeys'; export * from './useNodeOperatorsCount'; +export * from './useRewardsFrame'; export * from './useSearchParams'; export * from './useSessionStorage'; export * from './useStakingLimitInfo'; diff --git a/shared/hooks/useCsmContracts.ts b/shared/hooks/useCsmContracts.ts index 829d1664..668f2248 100644 --- a/shared/hooks/useCsmContracts.ts +++ b/shared/hooks/useCsmContracts.ts @@ -8,6 +8,7 @@ import { CSModule__factory, CSModuleOld__factory, ExitBusOracle__factory, + HashConsensus__factory, StakingRouter__factory, } from 'generated'; @@ -49,6 +50,13 @@ const CSFeeOracle = contractHooksFactory( export const useCSFeeOracleRPC = CSFeeOracle.useContractRPC; +const HashConsesus = contractHooksFactory( + HashConsensus__factory, + getCsmContractAddressGetter('HashConsensus'), +); + +export const useHashConsesusRPC = HashConsesus.useContractRPC; + const CSEarlyAdoption = contractHooksFactory( CSEarlyAdoption__factory, getCsmContractAddressGetter('CSEarlyAdoption'), diff --git a/shared/hooks/useLastRewardsFrame.ts b/shared/hooks/useLastRewardsReport.ts similarity index 78% rename from shared/hooks/useLastRewardsFrame.ts rename to shared/hooks/useLastRewardsReport.ts index 41cc7ba5..0e64e109 100644 --- a/shared/hooks/useLastRewardsFrame.ts +++ b/shared/hooks/useLastRewardsReport.ts @@ -10,44 +10,6 @@ import { useMemo } from 'react'; import { BigNumber } from 'ethers'; import { Zero } from '@ethersproject/constants'; -const SECONDS_PER_SLOT = 12; - -const getRewardsFrameDuration = () => { - const { slotsPerFrame } = getCsmConstants(); - return slotsPerFrame * SECONDS_PER_SLOT; -}; - -export const getNextRewardsFrame = (timestamp: number) => - timestamp + getRewardsFrameDuration(); - -export const getPrevRewardsFrame = (timestamp: number) => - timestamp - getRewardsFrameDuration(); - -export const useLastRewardsSlot = (config = STRATEGY_CONSTANT) => { - const feeOracle = useCSFeeOracleRPC(); - - return useLidoSWR( - ['fee-oracle-slot'], - async () => { - const [refSlot, genesisTime] = await Promise.all([ - feeOracle.getLastProcessingRefSlot(), - feeOracle.GENESIS_TIME(), - ]); - - if (!refSlot || !genesisTime) { - return null; - } - - const timestamp = genesisTime - .add(refSlot.mul(SECONDS_PER_SLOT)) - .toNumber(); - - return { refSlot, timestamp }; - }, - config, - ); -}; - export type RewardsReport = { blockstamp: { block_hash: HexString; diff --git a/shared/hooks/useRewardsFrame.ts b/shared/hooks/useRewardsFrame.ts new file mode 100644 index 00000000..3f629ee3 --- /dev/null +++ b/shared/hooks/useRewardsFrame.ts @@ -0,0 +1,62 @@ +import { useContractSWR } from '@lido-sdk/react'; +import { STRATEGY_CONSTANT, STRATEGY_IMMUTABLE } from 'consts/swr-strategies'; +import { useMemo } from 'react'; +import { useHashConsesusRPC } from './useCsmContracts'; +import { useMergeSwr } from './useMergeSwr'; + +export const useChainConfig = (config = STRATEGY_IMMUTABLE) => { + return useContractSWR({ + contract: useHashConsesusRPC(), + method: 'getChainConfig', + params: [], + config, + }); +}; + +export const useFrameConfig = (config = STRATEGY_IMMUTABLE) => { + return useContractSWR({ + contract: useHashConsesusRPC(), + method: 'getFrameConfig', + params: [], + config, + }); +}; + +export const useCurrentFrame = (config = STRATEGY_CONSTANT) => { + return useContractSWR({ + contract: useHashConsesusRPC(), + method: 'getCurrentFrame', + params: [], + config, + }); +}; + +export const useRewardsFrame = () => { + const chainConfig = useChainConfig(); + const frameConfig = useFrameConfig(); + const currentFrame = useCurrentFrame(); + + return useMergeSwr( + [chainConfig, currentFrame], + useMemo(() => { + if (!chainConfig.data || !frameConfig.data || !currentFrame.data) + return undefined; + + const timestamp = currentFrame.data.refSlot + .mul(chainConfig.data.secondsPerSlot) + .add(chainConfig.data.genesisTime) + .toNumber(); + + const duration = frameConfig.data.epochsPerFrame + .mul(chainConfig.data.slotsPerEpoch) + .mul(chainConfig.data.secondsPerSlot) + .toNumber(); + + return { + lastRewards: timestamp, + nextRewards: timestamp + duration, + prevRewards: timestamp - duration, + }; + }, [chainConfig.data, frameConfig.data, currentFrame.data]), + ); +}; diff --git a/utils/format-date.ts b/utils/format-date.ts index c8001f23..8b1e09cf 100644 --- a/utils/format-date.ts +++ b/utils/format-date.ts @@ -1,4 +1,9 @@ -import { format, fromUnixTime } from 'date-fns'; +import { differenceInCalendarDays, format, fromUnixTime } from 'date-fns'; export const formatDate = (timestamp?: number) => timestamp ? format(fromUnixTime(timestamp), 'MMM dd') : null; + +export const countDaysLeft = (timestamp?: number) => + timestamp + ? differenceInCalendarDays(fromUnixTime(timestamp), new Date()) + : null; diff --git a/utilsApi/contractAddressesMetricsMap.ts b/utilsApi/contractAddressesMetricsMap.ts index 4f2420f3..688a0ea1 100644 --- a/utilsApi/contractAddressesMetricsMap.ts +++ b/utilsApi/contractAddressesMetricsMap.ts @@ -23,6 +23,7 @@ import { CSFeeOracle__factory, CSModule__factory, ExitBusOracle__factory, + HashConsensus__factory, StakingRouter__factory, } from 'generated'; import { HexString } from 'shared/keys'; @@ -55,6 +56,7 @@ export const CONTRACT_NAMES = { CSAccounting: 'CSAccounting', CSFeeDistributor: 'CSFeeDistributor', CSFeeOracle: 'CSFeeOracle', + HashConsensus: 'HashConsensus', CSEarlyAdoption: 'CSEarlyAdoption', ExitBusOracle: 'ExitBusOracle', StakingRouter: 'StakingRouter', @@ -71,6 +73,7 @@ const CONTRACT_LIST_CALL: CONTRACT_NAMES[] = [ CONTRACT_NAMES.CSEarlyAdoption, CONTRACT_NAMES.CSFeeDistributor, CONTRACT_NAMES.CSFeeOracle, + CONTRACT_NAMES.HashConsensus, CONTRACT_NAMES.ExitBusOracle, CONTRACT_NAMES.StakingRouter, ]; @@ -90,6 +93,7 @@ export const METRIC_CONTRACT_ABIS = { [CONTRACT_NAMES.CSAccounting]: CSAccounting__factory.abi, [CONTRACT_NAMES.CSFeeDistributor]: CSFeeDistributor__factory.abi, [CONTRACT_NAMES.CSFeeOracle]: CSFeeOracle__factory.abi, + [CONTRACT_NAMES.HashConsensus]: HashConsensus__factory.abi, [CONTRACT_NAMES.CSEarlyAdoption]: CSEarlyAdoption__factory.abi, [CONTRACT_NAMES.ExitBusOracle]: ExitBusOracle__factory.abi, [CONTRACT_NAMES.StakingRouter]: StakingRouter__factory.abi, @@ -117,6 +121,10 @@ const METRIC_CONTRACT_ADDRESS_GETTERS = { getCsmContractAddress, 'CSFeeOracle', ), + [CONTRACT_NAMES.HashConsensus]: getAddressGetter( + getCsmContractAddress, + 'HashConsensus', + ), [CONTRACT_NAMES.CSEarlyAdoption]: getAddressGetter( getCsmContractAddress, 'CSEarlyAdoption', From 31c37dbeb8b1ddf35f36600f1a4af6cfbcfd46f2 Mon Sep 17 00:00:00 2001 From: exromany Date: Mon, 24 Mar 2025 17:26:09 +0700 Subject: [PATCH 22/58] chore: hoodi banner --- .../welcome/hoodi-banner/hoodi-banner.tsx | 37 +++++++++++++++++++ features/welcome/hoodi-banner/index.ts | 1 + features/welcome/hoodi-banner/style.ts | 20 ++++++++++ features/welcome/welcome.tsx | 2 + 4 files changed, 60 insertions(+) create mode 100644 features/welcome/hoodi-banner/hoodi-banner.tsx create mode 100644 features/welcome/hoodi-banner/index.ts create mode 100644 features/welcome/hoodi-banner/style.ts diff --git a/features/welcome/hoodi-banner/hoodi-banner.tsx b/features/welcome/hoodi-banner/hoodi-banner.tsx new file mode 100644 index 00000000..5669c781 --- /dev/null +++ b/features/welcome/hoodi-banner/hoodi-banner.tsx @@ -0,0 +1,37 @@ +import { DarkThemeProvider, Text } from '@lidofinance/lido-ui'; +import { FC } from 'react'; +import { MatomoLink } from 'shared/components'; +import { StyledAccordion } from './style'; + +export const HoodiBanner: FC<{ open?: boolean }> = ({ open }) => { + return ( + + + CSM is live on the Hoodi testnet! + + } + > + <> + + All testing activities regarding CSM will now be held on the Hoodi + testnet + + + CSM on Holesky is now deprecated. CSM Ui for Holesky is still + available on{' '} + + csm-holesky.testnet.fi + + + + + + ); +}; diff --git a/features/welcome/hoodi-banner/index.ts b/features/welcome/hoodi-banner/index.ts new file mode 100644 index 00000000..7e8e732c --- /dev/null +++ b/features/welcome/hoodi-banner/index.ts @@ -0,0 +1 @@ +export * from './hoodi-banner'; diff --git a/features/welcome/hoodi-banner/style.ts b/features/welcome/hoodi-banner/style.ts new file mode 100644 index 00000000..a713e513 --- /dev/null +++ b/features/welcome/hoodi-banner/style.ts @@ -0,0 +1,20 @@ +import { Accordion } from '@lidofinance/lido-ui'; +import styled from 'styled-components'; + +export const StyledAccordion = styled(Accordion)` + width: 100%; + + --first-color: #bfdbfe; + --second-color: #ccfbf1; + + background: radial-gradient( + 1435.85% 196.07% at 95.46% -44.7%, + rgba(34, 56, 255, 0.8) 0%, + rgba(235, 0, 255, 0.4) 100% + ), + linear-gradient(102deg, #bae6fd -8.89%, #93c5fd 105.62%); + + > div + div p { + margin-bottom: 0.5em; + } +`; diff --git a/features/welcome/welcome.tsx b/features/welcome/welcome.tsx index 5acdd433..25a9bb69 100644 --- a/features/welcome/welcome.tsx +++ b/features/welcome/welcome.tsx @@ -11,6 +11,7 @@ import styled from 'styled-components'; import { HoleskyBanner } from './holesky-banner'; import { getConfig } from 'config'; import { CHAINS } from 'consts/chains'; +import { HoodiBanner } from './hoodi-banner'; const { defaultChain } = getConfig(); @@ -24,6 +25,7 @@ export const Welcome: FC = () => { return ( <> {defaultChain === CHAINS.Holesky && paused && } + {defaultChain === CHAINS.Hoodi && } {isWrongChain && } From 6a3014d55473135dddf9200f46fe4231bca90be5 Mon Sep 17 00:00:00 2001 From: exromany Date: Mon, 24 Mar 2025 17:57:34 +0700 Subject: [PATCH 23/58] fix: metrics for aggregatorPriceFeed --- utilsApi/contractAddressesMetricsMap.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/utilsApi/contractAddressesMetricsMap.ts b/utilsApi/contractAddressesMetricsMap.ts index 688a0ea1..858262e9 100644 --- a/utilsApi/contractAddressesMetricsMap.ts +++ b/utilsApi/contractAddressesMetricsMap.ts @@ -103,8 +103,8 @@ const METRIC_CONTRACT_ADDRESS_GETTERS = { [CONTRACT_NAMES.stETH]: getAddressGetter(getTokenAddress, TOKENS.STETH), [CONTRACT_NAMES.wstETH]: getAddressGetter(getTokenAddress, TOKENS.WSTETH), [CONTRACT_NAMES.WithdrawalQueue]: getAddressGetter(getWithdrawalQueueAddress), - [CONTRACT_NAMES.aggregatorStEthUsdPriceFeed]: - getAddressGetter(getAggregatorAddress), + [CONTRACT_NAMES.aggregatorStEthUsdPriceFeed]: () => + getAddressGetter(getAggregatorAddress)(CHAINS.Mainnet), [CONTRACT_NAMES.CSModule]: getAddressGetter( getCsmContractAddress, 'CSModule', @@ -141,11 +141,10 @@ const METRIC_CONTRACT_ADDRESS_GETTERS = { const aggregatorMainnetAddress = METRIC_CONTRACT_ADDRESS_GETTERS[ CONTRACT_NAMES.aggregatorStEthUsdPriceFeed -](CHAINS.Mainnet) as HexString; +]() as HexString; const prefilledAddresses = - config.defaultChain === CHAINS.Hoodi && - !config.supportedChains.includes(CHAINS.Mainnet) + config.defaultChain !== CHAINS.Mainnet ? ({ [CHAINS.Mainnet]: [aggregatorMainnetAddress], } as Record) @@ -154,8 +153,7 @@ const prefilledAddresses = const prefilledMetricAddresses: Partial< Record> > = - config.defaultChain === CHAINS.Hoodi && - !config.supportedChains.includes(CHAINS.Mainnet) + config.defaultChain !== CHAINS.Mainnet ? { [CHAINS.Mainnet]: { [aggregatorMainnetAddress]: From 17241bac0c7d3131f37d4ba317b959df87e92a82 Mon Sep 17 00:00:00 2001 From: exromany Date: Mon, 24 Mar 2025 17:58:21 +0700 Subject: [PATCH 24/58] chore: correct gate rule for surveys page --- pages/surveys/[[...slug]].tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/surveys/[[...slug]].tsx b/pages/surveys/[[...slug]].tsx index e4203f9d..22cfe801 100644 --- a/pages/surveys/[[...slug]].tsx +++ b/pages/surveys/[[...slug]].tsx @@ -40,7 +40,7 @@ const Page = () => { return ( - }> + }> }> {page} From 595b3ed9df811c36d8b405039f7e5a8c725636e6 Mon Sep 17 00:00:00 2001 From: exromany Date: Mon, 24 Mar 2025 18:20:47 +0700 Subject: [PATCH 25/58] chore: holesky-banner fix: banner width --- .../paused-banner/paused-banner.tsx | 9 ---- .../welcome/holesky-banner/holesky-banner.tsx | 41 ++++--------------- features/welcome/welcome.tsx | 2 +- shared/components/banner/styles.ts | 1 + 4 files changed, 11 insertions(+), 42 deletions(-) diff --git a/features/starter-pack/paused-banner/paused-banner.tsx b/features/starter-pack/paused-banner/paused-banner.tsx index ebfa1e5c..bb907fed 100644 --- a/features/starter-pack/paused-banner/paused-banner.tsx +++ b/features/starter-pack/paused-banner/paused-banner.tsx @@ -1,17 +1,8 @@ import { FC } from 'react'; import { BannerHeader, BlockStyled } from './styles'; -import { getConfig } from 'config'; -import { CHAINS } from 'consts/chains'; -import { HoleskyBanner } from 'features/welcome/holesky-banner'; - -const { defaultChain } = getConfig(); export const PausedBanner: FC = () => { - if (defaultChain === CHAINS.Holesky) { - return ; - } - return ( CSM is paused diff --git a/features/welcome/holesky-banner/holesky-banner.tsx b/features/welcome/holesky-banner/holesky-banner.tsx index b1442188..09863a63 100644 --- a/features/welcome/holesky-banner/holesky-banner.tsx +++ b/features/welcome/holesky-banner/holesky-banner.tsx @@ -1,39 +1,16 @@ -import { DarkThemeProvider, Text } from '@lidofinance/lido-ui'; +import { DarkThemeProvider } from '@lidofinance/lido-ui'; import { FC } from 'react'; -import { Stack } from 'shared/components'; -import { StyledAccordion } from './style'; +import { Banner } from 'shared/components'; -export const HoleskyBanner: FC<{ open?: boolean }> = ({ open }) => { +export const HoleskyBanner: FC = () => { return ( - - - CSM is paused on Holesky - - - CSM is transitioning from the Holesky testnet to the Hoodi - testnet, and its operations on Holesky have been paused. - - - } - > - <> - - This means that uploading new keys is currently not possible, but - Node Operator stats can be viewed. - - - This update affects only CSM on Holesky testnet — CSM on Mainnet - remains fully operational. - - - Stay tuned for more details on the Hoodi testnet launch! - - - + +

+ This update affects only CSM on Holesky testnet — CSM on Mainnet + remains fully operational. +

+
); }; diff --git a/features/welcome/welcome.tsx b/features/welcome/welcome.tsx index 25a9bb69..ce725d0b 100644 --- a/features/welcome/welcome.tsx +++ b/features/welcome/welcome.tsx @@ -24,7 +24,7 @@ export const Welcome: FC = () => { return ( <> - {defaultChain === CHAINS.Holesky && paused && } + {defaultChain === CHAINS.Holesky && } {defaultChain === CHAINS.Hoodi && } {isWrongChain && } diff --git a/shared/components/banner/styles.ts b/shared/components/banner/styles.ts index 6fe18fcc..1bb1e692 100644 --- a/shared/components/banner/styles.ts +++ b/shared/components/banner/styles.ts @@ -52,6 +52,7 @@ export const BannerStyled = styled(Block)<{ $variant?: BannerVariant }>` display: flex; flex-direction: column; gap: ${({ theme }) => theme.spaceMap.md}px; + width: 100%; ${({ $variant }) => ($variant ? VARIANTS[$variant] : '')} `; From 9d35b502e9eeed1d522e8a0766acd18d636de94e Mon Sep 17 00:00:00 2001 From: exromany Date: Sat, 15 Mar 2025 19:41:37 +0800 Subject: [PATCH 26/58] feat: integrate shares to stETH conversion in LastRewards component --- features/dashboard/bond/last-rewards.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/features/dashboard/bond/last-rewards.tsx b/features/dashboard/bond/last-rewards.tsx index f57fd425..7d0b5b68 100644 --- a/features/dashboard/bond/last-rewards.tsx +++ b/features/dashboard/bond/last-rewards.tsx @@ -16,6 +16,7 @@ import { useLastRewrdsTx, useNodeOperatorInfo, useRewardsFrame, + useSharesToSteth, } from 'shared/hooks'; import { countDaysLeft, formatDate, formatPercent } from 'utils'; import { Balance } from './balance'; @@ -31,6 +32,8 @@ import { export const LastRewards: FC = () => { const { data: lastRewards, initialLoading: isLoading } = useLastOperatorRewards(); + const { data: distributed, initialLoading: isDistributedLoading } = + useSharesToSteth(lastRewards?.distributed); const id = useNodeOperatorId(); const { data: info } = useNodeOperatorInfo(id); @@ -62,8 +65,8 @@ export const LastRewards: FC = () => { : undefined} /> From e5f747ce3aa5a3d0a612338f8b163a5c392382d1 Mon Sep 17 00:00:00 2001 From: exromany Date: Mon, 24 Mar 2025 23:06:46 +0700 Subject: [PATCH 27/58] chore: optional country fields in setup survey --- features/surveys/survey-setup/survey-setup.tsx | 3 --- features/surveys/survey-setup/transform.tsx | 4 ++-- features/surveys/types.ts | 6 +++--- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/features/surveys/survey-setup/survey-setup.tsx b/features/surveys/survey-setup/survey-setup.tsx index 9def91de..57ee6ffb 100644 --- a/features/surveys/survey-setup/survey-setup.tsx +++ b/features/surveys/survey-setup/survey-setup.tsx @@ -216,7 +216,6 @@ export const SurveySetup: FC<{ id?: string }> = ({ id }) => { @@ -257,7 +256,6 @@ export const SurveySetup: FC<{ id?: string }> = ({ id }) => { )} @@ -279,7 +277,6 @@ export const SurveySetup: FC<{ id?: string }> = ({ id }) => { fieldName="mevMinBid" label="Min bid" token="ETH" - rules={required} /> Submit diff --git a/features/surveys/survey-setup/transform.tsx b/features/surveys/survey-setup/transform.tsx index 0c0d593c..b6e29600 100644 --- a/features/surveys/survey-setup/transform.tsx +++ b/features/surveys/survey-setup/transform.tsx @@ -3,13 +3,13 @@ import { Setup, SetupRaw } from '../types'; export const transformOutcoming = (data: Setup): SetupRaw => ({ ...data, - mevMinBid: data.mevMinBid?.toString(), + mevMinBid: data.mevMinBid?.toString() || null, validatorClient: data.validatorSameAsCl ? '' : data.validatorClient, validatorServerType: data.validatorSameAsCl ? '' : data.validatorServerType, validatorCountry: data.validatorSameAsCl ? '' : data.validatorCountry, }); export const transformIncoming = (data: SetupRaw): Setup => ({ ...data, - mevMinBid: BigNumber.from(data.mevMinBid), + mevMinBid: data.mevMinBid ? BigNumber.from(data.mevMinBid) : undefined, validatorSameAsCl: data.validatorSameAsCl ?? false, }); diff --git a/features/surveys/types.ts b/features/surveys/types.ts index abc1360d..ce5352e6 100644 --- a/features/surveys/types.ts +++ b/features/surveys/types.ts @@ -37,17 +37,17 @@ export type Setup = { elClient: string; clClient: string; clinetsServerType: string; - clientsCountry: string; + clientsCountry?: string; validatorClient: string; validatorServerType: string; - validatorCountry: string; + validatorCountry?: string; validatorSameAsCl?: boolean; remoteSigner: string; mevMinBid?: BigNumber; }; export type SetupRaw = Omit & { - mevMinBid?: string; + mevMinBid?: string | null; }; export type Summary = { From 62083aed99fe17bb9fef832ca8c984951208c980 Mon Sep 17 00:00:00 2001 From: exromany Date: Tue, 25 Mar 2025 01:13:57 +0700 Subject: [PATCH 28/58] feat: surveys cta before april --- features/dashboard/dashboard.tsx | 2 ++ features/dashboard/surveys-cta/index.ts | 1 + .../dashboard/surveys-cta/surveys-cta.tsx | 22 +++++++++++++++++++ shared/components/banner/banner.tsx | 21 +++++++++++++++--- shared/counters/counter-surveys.tsx | 10 +++++++++ shared/counters/index.ts | 1 + shared/hooks/index.ts | 1 + shared/hooks/use-surveys-call.ts | 7 ++++++ .../components/navigation/use-nav-items.tsx | 2 ++ 9 files changed, 64 insertions(+), 3 deletions(-) create mode 100644 features/dashboard/surveys-cta/index.ts create mode 100644 features/dashboard/surveys-cta/surveys-cta.tsx create mode 100644 shared/counters/counter-surveys.tsx create mode 100644 shared/hooks/use-surveys-call.ts diff --git a/features/dashboard/dashboard.tsx b/features/dashboard/dashboard.tsx index ee7910f8..8abdeb5d 100644 --- a/features/dashboard/dashboard.tsx +++ b/features/dashboard/dashboard.tsx @@ -5,12 +5,14 @@ import { RolesSection } from './roles'; import { ExternalSection } from './external'; import { getConfig } from 'config'; import { CHAINS } from 'consts/chains'; +import { SurveysCta } from './surveys-cta'; const { defaultChain } = getConfig(); export const Dashboard: FC = () => { return ( <> + diff --git a/features/dashboard/surveys-cta/index.ts b/features/dashboard/surveys-cta/index.ts new file mode 100644 index 00000000..90f421ae --- /dev/null +++ b/features/dashboard/surveys-cta/index.ts @@ -0,0 +1 @@ +export * from './surveys-cta'; diff --git a/features/dashboard/surveys-cta/surveys-cta.tsx b/features/dashboard/surveys-cta/surveys-cta.tsx new file mode 100644 index 00000000..e4abb8df --- /dev/null +++ b/features/dashboard/surveys-cta/surveys-cta.tsx @@ -0,0 +1,22 @@ +import { FC } from 'react'; +import { Banner } from 'shared/components'; +import { useSurveysCall } from 'shared/hooks'; +import { LocalLink } from 'shared/navigate'; + +export const SurveysCta: FC = () => { + const required = useSurveysCall(); + if (!required) return null; + + return ( + + You're invited to voluntarily submit your validator setup data by + March 31st to help enhance the transparency of the Lido Protocol! Go to + the Surveys tab and fill out the + "Your Setup" form. + + ); +}; diff --git a/shared/components/banner/banner.tsx b/shared/components/banner/banner.tsx index e6655b11..a01a118b 100644 --- a/shared/components/banner/banner.tsx +++ b/shared/components/banner/banner.tsx @@ -1,12 +1,19 @@ -import { FC, PropsWithChildren, ReactNode } from 'react'; +import { ComponentProps, FC, PropsWithChildren, ReactNode } from 'react'; import { BannerContent, BannerHeader, BannerStyled, BannerVariant, } from './styles'; +import { LocalLink } from 'shared/navigate'; +import { Stack } from '../stack/stack'; +import { SectionHeaderLinkStyle } from '../section-title/styles'; -type BannerProps = { +import { ReactComponent as RoundedArrowIcon } from 'assets/icons/rounded-arrow.svg'; + +type BannerProps = Partial< + Pick, 'href' | 'matomoEvent'> +> & { title?: ReactNode; variant?: BannerVariant; }; @@ -14,10 +21,18 @@ type BannerProps = { export const Banner: FC> = ({ title, variant, + href, children, }) => ( - {title} + + {title} + {!!href && ( + + + + )} + {children} ); diff --git a/shared/counters/counter-surveys.tsx b/shared/counters/counter-surveys.tsx new file mode 100644 index 00000000..75eb55ae --- /dev/null +++ b/shared/counters/counter-surveys.tsx @@ -0,0 +1,10 @@ +import { FC } from 'react'; +import { Counter } from 'shared/components'; +import { useSurveysCall } from 'shared/hooks'; + +export const CounterSurveys: FC = () => { + const required = useSurveysCall(); + const count = Number(required); + + return ; +}; diff --git a/shared/counters/index.ts b/shared/counters/index.ts index 0e9741a2..eda4aacb 100644 --- a/shared/counters/index.ts +++ b/shared/counters/index.ts @@ -1,3 +1,4 @@ export * from './counter-invalid-keys'; export * from './counter-invites'; export * from './counter-locked-bond'; +export * from './counter-surveys'; diff --git a/shared/hooks/index.ts b/shared/hooks/index.ts index cc9a1183..01cdd3c7 100644 --- a/shared/hooks/index.ts +++ b/shared/hooks/index.ts @@ -35,6 +35,7 @@ export * from './use-router-path'; export * from './use-send-tx'; export * from './use-show-rule'; export * from './use-sorted-keys'; +export * from './use-surveys-call'; export * from './use-token-max-amount'; export * from './use-tx-cost-in-usd'; export * from './use-withdrawn-key-indexes-from-events'; diff --git a/shared/hooks/use-surveys-call.ts b/shared/hooks/use-surveys-call.ts new file mode 100644 index 00000000..d85bd4f7 --- /dev/null +++ b/shared/hooks/use-surveys-call.ts @@ -0,0 +1,7 @@ +import { isBefore, parseISO } from 'date-fns'; + +export const useSurveysCall = () => { + const endOfSurvey = parseISO('2025-04-01T00:00Z'); + const today = new Date(); + return isBefore(today, endOfSurvey); +}; diff --git a/shared/layout/header/components/navigation/use-nav-items.tsx b/shared/layout/header/components/navigation/use-nav-items.tsx index bf63b32f..2d5d8549 100644 --- a/shared/layout/header/components/navigation/use-nav-items.tsx +++ b/shared/layout/header/components/navigation/use-nav-items.tsx @@ -12,6 +12,7 @@ import { CounterInvalidKeys, CounterInvites, CounterLockedBond, + CounterSurveys, } from 'shared/counters'; import { ShowRule, useShowRule } from 'shared/hooks'; @@ -73,6 +74,7 @@ const routes: Route[] = [ path: PATH.SURVEYS, icon: , showRules: ['IS_SURVEYS_ACTIVE'], + suffix: , }, ]; From c6027c36eccb28c295bc6be761f9e210a3c2447b Mon Sep 17 00:00:00 2001 From: exromany Date: Tue, 25 Mar 2025 16:22:40 +0700 Subject: [PATCH 29/58] fix: hide beaconcha.in on hoodi --- consts/external-links.ts | 4 ++-- shared/hooks/use-external-links.ts | 2 ++ .../tx-stages-parts/after-keys-upload.tsx | 18 +++++++++++------- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/consts/external-links.ts b/consts/external-links.ts index e874c728..c3952cae 100644 --- a/consts/external-links.ts +++ b/consts/external-links.ts @@ -92,8 +92,8 @@ export const EXTERNAL_LINKS_BY_NETWORK: Record = feesMonitoring: 'https://fees-monitoring-hoodi.testnet.fi', operatorsWidget: 'https://operators-hoodi.testnet.fi', - beaconchain: 'https://holesky.beaconcha.in', - beaconchainDashboard: 'https://v2-beta-holesky.beaconcha.in/dashboard', + beaconchain: '', + beaconchainDashboard: '', ratedExplorer: 'https://explorer.rated.network', subscribeEvents: 'https://docs.lido.fi/staking-modules/csm/guides/events', keysApi: 'http://hr6vb81d1ndsx-hoodi-keys-api.valset-01.testnet.fi', diff --git a/shared/hooks/use-external-links.ts b/shared/hooks/use-external-links.ts index 4d129f74..6a0c7465 100644 --- a/shared/hooks/use-external-links.ts +++ b/shared/hooks/use-external-links.ts @@ -15,6 +15,8 @@ export const useBeaconchainDashboardLink = (directKeys?: string[]) => { const { data: keys } = useKeysWithStatus(); const sortedKeys = useSortedKeys(keys, ACTIVE_STATUS_ORDER); + if (!links.beaconchainDashboard) return null; + const keysToShow = ( sortedKeys?.length ? sortedKeys.map(({ key }) => key) : directKeys ) diff --git a/shared/transaction-modal/tx-stages-parts/after-keys-upload.tsx b/shared/transaction-modal/tx-stages-parts/after-keys-upload.tsx index dea05714..e98b1e9a 100644 --- a/shared/transaction-modal/tx-stages-parts/after-keys-upload.tsx +++ b/shared/transaction-modal/tx-stages-parts/after-keys-upload.tsx @@ -30,13 +30,17 @@ export const AfterKeysUpload: FC = ({ keys }) => { > Keys tab - , on{' '} - - beaconcha.in - {' '} + {beaconchain && ( + <> + , on{' '} + + beaconcha.in + + + )}{' '} or subscribe to the{' '} Date: Tue, 25 Mar 2025 18:42:58 +0700 Subject: [PATCH 30/58] chore: hide setups on testnet --- consts/urls.ts | 1 + features/surveys/surveys-home-page.tsx | 4 +-- .../surveys/surveys-home/surverys-home.tsx | 25 ++++++++++++++----- pages/surveys/[[...slug]].tsx | 2 ++ 4 files changed, 24 insertions(+), 8 deletions(-) diff --git a/consts/urls.ts b/consts/urls.ts index 35c5d810..b738260f 100644 --- a/consts/urls.ts +++ b/consts/urls.ts @@ -26,6 +26,7 @@ export const PATH = { SURVEYS_EXPERIENCE: '/surveys/experience', SURVEYS_HOW_DID_YOU_LEARN_CSM: '/surveys/learn-csm', SURVEYS_SETUP: '/surveys/setup', + SURVEYS_ALL: '/surveys/all', }; export type PATH = (typeof PATH)[keyof typeof PATH]; diff --git a/features/surveys/surveys-home-page.tsx b/features/surveys/surveys-home-page.tsx index 09ddb197..778f513c 100644 --- a/features/surveys/surveys-home-page.tsx +++ b/features/surveys/surveys-home-page.tsx @@ -4,10 +4,10 @@ import { NoSSRWrapper } from 'shared/components'; import { Layout } from 'shared/layout'; import { SurveysHome } from './surveys-home/surverys-home'; -export const SurveysHomePage: FC = () => ( +export const SurveysHomePage: FC<{ all?: boolean }> = ({ all }) => ( - + ); diff --git a/features/surveys/surveys-home/surverys-home.tsx b/features/surveys/surveys-home/surverys-home.tsx index 21e5f9c0..64c982e8 100644 --- a/features/surveys/surveys-home/surverys-home.tsx +++ b/features/surveys/surveys-home/surverys-home.tsx @@ -12,8 +12,12 @@ import { useSurveysSWR } from '../shared/use-surveys-swr'; import { useConfirmEraseModal } from './confirm-erase-modal'; import { Divider, Plus, Text } from '@lidofinance/lido-ui'; import { SetupsKeys, Summary } from '../types'; +import { CHAINS } from 'consts/chains'; +import { getConfig } from 'config'; -export const SurveysHome: FC = () => { +const { defaultChain } = getConfig(); + +export const SurveysHome: FC<{ all?: boolean }> = ({ all }) => { const { data, isLoading, remove } = useSurveysSWR('summary'); const { data: keys, mutate: mutateKeys } = useSurveysSWR('setups/keys'); @@ -27,6 +31,18 @@ export const SurveysHome: FC = () => { } }, [confirmModal, mutateKeys, remove]); + const showErase = !!( + data?.contacts || + data?.experience || + data?.howDidYouLearnCsm || + (data?.setups && data.setups.length > 0) + ); + const showSetups = !!( + (all || defaultChain === CHAINS.Mainnet) && + keys && + (keys.total > 0 || keys.filled > 0) + ); + return ( { - {keys && (keys.total > 0 || keys.filled > 0) && ( + {showSetups && ( { )} - {(data?.contacts || - data?.experience || - data?.howDidYouLearnCsm || - (data?.setups && data.setups.length > 0)) && ( + {showErase && ( <> diff --git a/pages/surveys/[[...slug]].tsx b/pages/surveys/[[...slug]].tsx index 22cfe801..f58a423b 100644 --- a/pages/surveys/[[...slug]].tsx +++ b/pages/surveys/[[...slug]].tsx @@ -32,6 +32,8 @@ const Page = () => { return ; case PATH.SURVEYS_SETUP: return ; + case PATH.SURVEYS_ALL: + return ; default: return ; From a0673973615fe54d38fafb2956a5b79727220b7f Mon Sep 17 00:00:00 2001 From: exromany Date: Tue, 25 Mar 2025 23:07:11 +0700 Subject: [PATCH 31/58] chore: return etherscan on hoodi --- faq/hoodi/keys-1.md | 4 ++-- .../external-icon-link/etherscan-address-link.tsx | 10 +--------- .../components/tx-link-etherscan/tx-link-etherscan.tsx | 10 +--------- 3 files changed, 4 insertions(+), 20 deletions(-) diff --git a/faq/hoodi/keys-1.md b/faq/hoodi/keys-1.md index 08ec2b0e..017eaffc 100644 --- a/faq/hoodi/keys-1.md +++ b/faq/hoodi/keys-1.md @@ -6,8 +6,8 @@ A detailed guide on preparing all the validation tools for CSM can be found [her A shorter flow of setting up a CSM validator for **testnet** looks as follows: -1. [Generate new validator keys](https://dvt-homestaker.stakesaurus.com/keystore-generation-and-mev-boost/validator-key-generation) setting the `withdrawal_address` to the [Lido Withdrawal Vault](https://hoodi.cloud.blockscout.com/address/0x4473dCDDbf77679A643BdB654dbd86D67F8d32f2) on **Hoodi:** [`0x4473dCDDbf77679A643BdB654dbd86D67F8d32f2`](https://hoodi.cloud.blockscout.com/address/0x4473dCDDbf77679A643BdB654dbd86D67F8d32f2) and specify the deposit amount of 32 ETH (do **NOT** make a deposit) -2. [Configure your validator client](https://dvt-homestaker.stakesaurus.com/native-solo-staking-setup/validator-client-setup) (and/or beacon node) setting the `fee_recipient` flag to the designated fee recipient address (Lido Execution Layer Rewards Vault) on **Hoodi:** [`0x9b108015fe433F173696Af3Aa0CF7CDb3E104258`](https://hoodi.cloud.blockscout.com/address/0x9b108015fe433F173696Af3Aa0CF7CDb3E104258) and import the newly generated CSM keystores +1. [Generate new validator keys](https://dvt-homestaker.stakesaurus.com/keystore-generation-and-mev-boost/validator-key-generation) setting the `withdrawal_address` to the [Lido Withdrawal Vault](https://hoodi.etherscan.io/address/0x4473dCDDbf77679A643BdB654dbd86D67F8d32f2) on **Hoodi:** [`0x4473dCDDbf77679A643BdB654dbd86D67F8d32f2`](https://hoodi.etherscan.io/address/0x4473dCDDbf77679A643BdB654dbd86D67F8d32f2) and specify the deposit amount of 32 ETH (do **NOT** make a deposit) +2. [Configure your validator client](https://dvt-homestaker.stakesaurus.com/native-solo-staking-setup/validator-client-setup) (and/or beacon node) setting the `fee_recipient` flag to the designated fee recipient address (Lido Execution Layer Rewards Vault) on **Hoodi:** [`0x9b108015fe433F173696Af3Aa0CF7CDb3E104258`](https://hoodi.etherscan.io/address/0x9b108015fe433F173696Af3Aa0CF7CDb3E104258) and import the newly generated CSM keystores 3. **Do not setup any MEV-Boost** 4. [Upload the newly generated deposit data](https://dvt-homestaker.stakesaurus.com/bonded-validators-setup/lido-csm/upload-remove-view-validator-keys) file pertaining to your CSM keystores onto [the Lido CSM Widget](https://csm.testnet.fi/) and provide the required bond amount in Hoodi ETH/stETH/wstETH. Before uploading, make sure that nodes are synced, running, and ready for the validator activation. 5. Wait for your CSM validator keys to be deposited through the protocol and make sure your node remains online in the meantime! diff --git a/shared/components/external-icon-link/etherscan-address-link.tsx b/shared/components/external-icon-link/etherscan-address-link.tsx index 87665d93..40bf10af 100644 --- a/shared/components/external-icon-link/etherscan-address-link.tsx +++ b/shared/components/external-icon-link/etherscan-address-link.tsx @@ -4,19 +4,11 @@ import { MATOMO_CLICK_EVENTS_TYPES } from 'consts/matomo-click-events'; import { FC } from 'react'; import { useAccount } from 'shared/hooks'; import { MatomoLink } from '../matomo-link/matomo-link'; -import { CHAINS } from '@lido-sdk/constants'; type Props = { address: string; }; -const getExplorerAddressLink = (chainId: number, address: string) => { - if (chainId === CHAINS.Hoodi) { - return `https://hoodi.cloud.blockscout.com/address/${address}`; - } - return getEtherscanAddressLink(chainId, address); -}; - export const EtherscanAddressLink: FC = ({ address }) => { const { chainId } = useAccount(); @@ -24,7 +16,7 @@ export const EtherscanAddressLink: FC = ({ address }) => { return ( diff --git a/shared/components/tx-link-etherscan/tx-link-etherscan.tsx b/shared/components/tx-link-etherscan/tx-link-etherscan.tsx index 4f1a21cb..cd5c881c 100644 --- a/shared/components/tx-link-etherscan/tx-link-etherscan.tsx +++ b/shared/components/tx-link-etherscan/tx-link-etherscan.tsx @@ -3,20 +3,12 @@ import { getEtherscanTxLink } from '@lido-sdk/helpers'; import { MatomoLink } from '../matomo-link/matomo-link'; import { MATOMO_CLICK_EVENTS_TYPES } from 'consts/matomo-click-events'; import { FC } from 'react'; -import { CHAINS } from '@lido-sdk/constants'; type TxLinkEtherscanProps = { text?: string; txHash?: string | null; }; -const getExplorerTxLink = (chainId: number, hash: string) => { - if (chainId === CHAINS.Hoodi) { - return `https://hoodi.cloud.blockscout.com/tx/${hash}`; - } - return getEtherscanTxLink(chainId, hash); -}; - export const TxLinkEtherscan: FC = ({ txHash, text = 'View on Etherscan', @@ -27,7 +19,7 @@ export const TxLinkEtherscan: FC = ({ return ( {text} From 2c6467082e50cc0956024e4ff25de1fcdf2d35a8 Mon Sep 17 00:00:00 2001 From: exromany Date: Tue, 25 Mar 2025 23:09:33 +0700 Subject: [PATCH 32/58] chore: hoodi beaconcha.in key link --- consts/external-links.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/consts/external-links.ts b/consts/external-links.ts index c3952cae..a97bc430 100644 --- a/consts/external-links.ts +++ b/consts/external-links.ts @@ -92,7 +92,7 @@ export const EXTERNAL_LINKS_BY_NETWORK: Record = feesMonitoring: 'https://fees-monitoring-hoodi.testnet.fi', operatorsWidget: 'https://operators-hoodi.testnet.fi', - beaconchain: '', + beaconchain: 'https://hoodi.beaconcha.in', beaconchainDashboard: '', ratedExplorer: 'https://explorer.rated.network', subscribeEvents: 'https://docs.lido.fi/staking-modules/csm/guides/events', From 05e85a92e8e5036cb6246ad31be52999fd260101 Mon Sep 17 00:00:00 2001 From: exromany Date: Tue, 25 Mar 2025 23:14:13 +0700 Subject: [PATCH 33/58] chore: hoodi disable surveys banner --- shared/hooks/use-surveys-call.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/shared/hooks/use-surveys-call.ts b/shared/hooks/use-surveys-call.ts index d85bd4f7..3c16e02a 100644 --- a/shared/hooks/use-surveys-call.ts +++ b/shared/hooks/use-surveys-call.ts @@ -1,7 +1,11 @@ +import { getConfig } from 'config'; +import { CHAINS } from 'consts/chains'; import { isBefore, parseISO } from 'date-fns'; +const { defaultChain } = getConfig(); + export const useSurveysCall = () => { const endOfSurvey = parseISO('2025-04-01T00:00Z'); const today = new Date(); - return isBefore(today, endOfSurvey); + return defaultChain === CHAINS.Mainnet && isBefore(today, endOfSurvey); }; From c285f4ca33cc4ac9c68ec8702f4a9f22596bd630 Mon Sep 17 00:00:00 2001 From: exromany Date: Wed, 26 Mar 2025 18:47:56 +0700 Subject: [PATCH 34/58] chore: add bond text --- features/add-bond/add-bond-form/controls/info.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/features/add-bond/add-bond-form/controls/info.tsx b/features/add-bond/add-bond-form/controls/info.tsx index 54bf12f1..1e275999 100644 --- a/features/add-bond/add-bond-form/controls/info.tsx +++ b/features/add-bond/add-bond-form/controls/info.tsx @@ -1,4 +1,4 @@ -import { BOND_EXCESS, BOND_INSUFFICIENT } from 'consts/text'; +import { BOND_INSUFFICIENT } from 'consts/text'; import { TOKENS } from 'consts/tokens'; import { FC } from 'react'; import { Latice, MatomoLink, Stack, TitledAmount } from 'shared/components'; @@ -13,11 +13,11 @@ export const Info: FC = () => { { {bond?.isInsufficient ? (

Your Node Operator has an Insufficient bond because of the penalty - applied. Now your Node Operator’s bond is less than required to - cover the Node Operator’s current validators. + applied. Now your Node Operator's bond is less than required + to cover the Node Operator's current validators.
Action required:
From 5551d715c00da83d81390438ab465d4236429a0d Mon Sep 17 00:00:00 2001 From: exromany Date: Wed, 26 Mar 2025 19:12:18 +0700 Subject: [PATCH 35/58] chore: claim bond tooltips --- .../claim-bond-form/claim-bond-form-info.tsx | 3 +++ .../claim-bond-form/controls/source-select.tsx | 14 ++++++++++---- features/dashboard/bond/available-to-claim.tsx | 4 ++-- features/dashboard/bond/bond-balance.tsx | 4 ++-- features/dashboard/bond/last-rewards.tsx | 2 +- .../remove-keys/remove-keys-form-info.tsx | 2 +- features/surveys/surveys-home/surverys-home.tsx | 6 +++--- .../unlock-bond/unlock-bond-form/controls/info.tsx | 2 +- .../titled-selectable-amount.tsx | 6 ++++-- 9 files changed, 27 insertions(+), 16 deletions(-) diff --git a/features/claim-bond/claim-bond-form/claim-bond-form-info.tsx b/features/claim-bond/claim-bond-form/claim-bond-form-info.tsx index 3c5994f4..9a4db2fd 100644 --- a/features/claim-bond/claim-bond-form/claim-bond-form-info.tsx +++ b/features/claim-bond/claim-bond-form/claim-bond-form-info.tsx @@ -32,6 +32,7 @@ export const ClaimBondFormInfo = () => {

) will receive } + help="The recipient of the claim is the Rewards address. You can change the Rewards address on the Roles tab" > @@ -45,6 +46,8 @@ export const ClaimBondFormInfo = () => { }; const AddressStyled = styled.div` + display: inline; + ${AddressContainerStyle} { display: inline-flex; } diff --git a/features/claim-bond/claim-bond-form/controls/source-select.tsx b/features/claim-bond/claim-bond-form/controls/source-select.tsx index 63357616..81b41deb 100644 --- a/features/claim-bond/claim-bond-form/controls/source-select.tsx +++ b/features/claim-bond/claim-bond-form/controls/source-select.tsx @@ -10,10 +10,13 @@ import { Stack, TitledSelectableAmount, } from 'shared/components'; +import { useRewardsFrame } from 'shared/hooks'; +import { formatDate } from 'utils'; import { ClaimBondFormInputType, useClaimBondFormData } from '../context'; export const SourceSelect: FC = () => { const { bond, rewards, loading, maxValues } = useClaimBondFormData(); + const { data: rewardsFrame } = useRewardsFrame(); const { field } = useController({ name: 'claimRewards', @@ -23,6 +26,7 @@ export const SourceSelect: FC = () => { const { setValue } = useFormContext(); const availableToClaim = maxValues?.[TOKENS.STETH][Number(field.value)]; + const nextRewardsDate = formatDate(rewardsFrame?.nextRewards); useEffect(() => { if (bond?.isInsufficient) { @@ -54,7 +58,8 @@ export const SourceSelect: FC = () => { disabled={!rewards?.available.gt(0)} /> } - help="The rewards amount available to claim, obtained from all active validators of the Node Operator" + help={`The rewards amount available to claim, obtained from all active validators of the Node Operator. Next rewards distribution is expected on ${nextRewardsDate}`} + helpIcon="calendar" loading={loading.isRewardsLoading} amount={rewards?.available} token={TOKENS.STETH} @@ -70,9 +75,10 @@ export const SourceSelect: FC = () => { } help={ bond?.isInsufficient - ? 'Insufficient bond is the missing amount of stETH required to cover all operator’s keys. In case of a bond insufficient, "unbonded" validators are requested for exit by the protocol' - : 'The bond amount available to claim without having to exit validators' + ? 'Insufficient bond is the missing amount of stETH required to cover all operator’s keys' + : 'The bond amount available to claim without having to exit validators. Increases daily' } + helpIcon={bond?.isInsufficient ? undefined : 'calendar'} sign={bond?.isInsufficient ? 'minus' : 'plus'} loading={loading.isBondLoading} amount={bond?.delta} @@ -82,7 +88,7 @@ export const SourceSelect: FC = () => { } - help="Bond may be locked in the case of an MEV stealing event reported by a dedicated committee. This measure ensures that Node Operators are held accountable for any misbehavior or rule violations." + help="Bond may be locked in the case of an MEV stealing event reported by a dedicated committee. This measure ensures that Node Operators are held accountable for any misbehavior or rule violations" loading={loading.isBondLoading} amount={bond?.locked} token={TOKENS.ETH} diff --git a/features/dashboard/bond/available-to-claim.tsx b/features/dashboard/bond/available-to-claim.tsx index fd80c9c2..9f454bbe 100644 --- a/features/dashboard/bond/available-to-claim.tsx +++ b/features/dashboard/bond/available-to-claim.tsx @@ -68,7 +68,7 @@ export const AvailableToClaim: FC = () => { warning sign="minus" title={BOND_INSUFFICIENT} - help="Insufficient bond is the missing amount of stETH required to cover all operator’s keys." + help="Insufficient bond is the missing amount of stETH required to cover all operator’s keys" loading={isBondLoading} amount={bond.delta} /> @@ -97,7 +97,7 @@ export const AvailableToClaim: FC = () => { loading={isBondLoading} amount={bond.locked} token={TOKENS.ETH} - help="Bond is locked because of an MEV stealing event reported by a dedicated committee. This measure ensures that Node Operators are held accountable for any misbehavior or rule violations." + help="Bond is locked because of an MEV stealing event reported by a dedicated committee. This measure ensures that Node Operators are held accountable for any misbehavior or rule violations" /> )} diff --git a/features/dashboard/bond/bond-balance.tsx b/features/dashboard/bond/bond-balance.tsx index 6e5d64fd..4f7bd1e6 100644 --- a/features/dashboard/bond/bond-balance.tsx +++ b/features/dashboard/bond/bond-balance.tsx @@ -46,7 +46,7 @@ export const BondBalance: FC = () => { title={BOND_INSUFFICIENT} loading={isBondLoading} amount={bond?.delta} - help="Insufficient bond is the missing amount of stETH required to cover all operator’s keys." + help="Insufficient bond is the missing amount of stETH required to cover all operator’s keys" /> ) : ( @@ -56,7 +56,7 @@ export const BondBalance: FC = () => { title={BOND_EXCESS} loading={isBondLoading} amount={bond?.delta} - help="The bond amount available to claim without having to exit validators. Increases daily." + help="The bond amount available to claim without having to exit validators. Increases daily" /> )} diff --git a/features/dashboard/bond/last-rewards.tsx b/features/dashboard/bond/last-rewards.tsx index 6169e010..673e4d36 100644 --- a/features/dashboard/bond/last-rewards.tsx +++ b/features/dashboard/bond/last-rewards.tsx @@ -147,7 +147,7 @@ const LastReportStats: FC = () => { title="Stuck keys found" loading={isLoading} warning={lastRewards?.stuck} - help="Indicates whether any of your Node Operator keys were marked as “Stuck” during the latest report frame. Stuck keys prevent the Node Operator from receiving rewards for any key(s) in that frame." + help="Indicates whether any of your Node Operator keys were marked as “Stuck” during the latest report frame. Stuck keys prevent the Node Operator from receiving rewards for any key(s) in that frame" > {lastRewards?.stuck ? 'YES' : 'NO'} diff --git a/features/remove-keys/remove-keys/remove-keys-form-info.tsx b/features/remove-keys/remove-keys/remove-keys-form-info.tsx index dd8edf9f..8f9cdb1f 100644 --- a/features/remove-keys/remove-keys/remove-keys-form-info.tsx +++ b/features/remove-keys/remove-keys/remove-keys-form-info.tsx @@ -24,7 +24,7 @@ export const RemoveKeysFormInfo = () => { diff --git a/features/surveys/surveys-home/surverys-home.tsx b/features/surveys/surveys-home/surverys-home.tsx index 64c982e8..107f7f79 100644 --- a/features/surveys/surveys-home/surverys-home.tsx +++ b/features/surveys/surveys-home/surverys-home.tsx @@ -48,7 +48,7 @@ export const SurveysHome: FC<{ all?: boolean }> = ({ all }) => { = ({ all }) => { = ({ all }) => { {keys && keys.filled > keys.total && ( diff --git a/features/unlock-bond/unlock-bond-form/controls/info.tsx b/features/unlock-bond/unlock-bond-form/controls/info.tsx index f8e6a9fe..10b1263c 100644 --- a/features/unlock-bond/unlock-bond-form/controls/info.tsx +++ b/features/unlock-bond/unlock-bond-form/controls/info.tsx @@ -12,7 +12,7 @@ export const Info: FC = () => { ['type']; warning?: boolean; loading?: boolean; amount?: BigNumber; @@ -17,6 +18,7 @@ type Props = { export const TitledSelectableAmount: FC = ({ title, help, + helpIcon, warning, ...props }) => { @@ -24,7 +26,7 @@ export const TitledSelectableAmount: FC = ({ {title} - + From 5318f6256dda82fc679a5808d152f7546934f36a Mon Sep 17 00:00:00 2001 From: exromany Date: Wed, 26 Mar 2025 21:29:14 +0700 Subject: [PATCH 36/58] feat: redirect to invites --- .../starter-pack/invites-redirect/index.ts | 1 + .../invites-redirect/invites-redirect.tsx | 22 +++++++++++++++++++ features/starter-pack/starter-pack-page.tsx | 4 +++- 3 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 features/starter-pack/invites-redirect/index.ts create mode 100644 features/starter-pack/invites-redirect/invites-redirect.tsx diff --git a/features/starter-pack/invites-redirect/index.ts b/features/starter-pack/invites-redirect/index.ts new file mode 100644 index 00000000..c9c75d6e --- /dev/null +++ b/features/starter-pack/invites-redirect/index.ts @@ -0,0 +1 @@ +export * from './invites-redirect'; diff --git a/features/starter-pack/invites-redirect/invites-redirect.tsx b/features/starter-pack/invites-redirect/invites-redirect.tsx new file mode 100644 index 00000000..8a6dede9 --- /dev/null +++ b/features/starter-pack/invites-redirect/invites-redirect.tsx @@ -0,0 +1,22 @@ +import { PATH } from 'consts/urls'; +import { FC, useEffect } from 'react'; +import { useInvites, useSessionStorage } from 'shared/hooks'; +import { useNavigate } from 'shared/navigate'; + +export const InvitesRedirect: FC = () => { + const { data: invites } = useInvites(); + const navigate = useNavigate(); + const [skipRedirect, setSkipRedirect] = useSessionStorage( + 'skip-invites-redirect', + false, + ); + + useEffect(() => { + if (invites?.length && !skipRedirect) { + setSkipRedirect(true); + void navigate(PATH.ROLES_INBOX); + } + }, [invites?.length, navigate, setSkipRedirect, skipRedirect]); + + return null; +}; diff --git a/features/starter-pack/starter-pack-page.tsx b/features/starter-pack/starter-pack-page.tsx index 67ebd879..68fc1e6c 100644 --- a/features/starter-pack/starter-pack-page.tsx +++ b/features/starter-pack/starter-pack-page.tsx @@ -1,8 +1,9 @@ import { FC } from 'react'; +import { MATOMO_CLICK_EVENTS_TYPES } from 'consts/matomo-click-events'; import { Layout } from 'shared/layout'; +import { InvitesRedirect } from './invites-redirect'; import { StarterPack } from './starter-pack'; -import { MATOMO_CLICK_EVENTS_TYPES } from 'consts/matomo-click-events'; export const StarterPackPage: FC = () => { return ( @@ -10,6 +11,7 @@ export const StarterPackPage: FC = () => { title="Community Staking Module" matomoEvent={MATOMO_CLICK_EVENTS_TYPES.pageStarterPack} > + ); From 5fe90a8314f015e6aa6145484c290ed2d880e468 Mon Sep 17 00:00:00 2001 From: exromany Date: Wed, 26 Mar 2025 21:47:24 +0700 Subject: [PATCH 37/58] chore: add tooltips to invite's role badge --- .../invite-content/invite-content.tsx | 36 ++++++++++++++++++- shared/components/invite-content/style.ts | 15 ++++++-- styles/global.ts | 2 +- 3 files changed, 48 insertions(+), 5 deletions(-) diff --git a/shared/components/invite-content/invite-content.tsx b/shared/components/invite-content/invite-content.tsx index ba36eb87..5cc4bfa6 100644 --- a/shared/components/invite-content/invite-content.tsx +++ b/shared/components/invite-content/invite-content.tsx @@ -2,12 +2,46 @@ import { FC } from 'react'; import { DescriptorId, getRoleTitle } from 'shared/node-operator'; import { NodeOperatorInvite } from 'types'; import { Badge, InviteContentStyle } from './style'; +import { Tooltip } from '@lidofinance/lido-ui'; +import { ROLES } from 'consts/roles'; export const InviteContent: FC<{ invite: NodeOperatorInvite }> = ({ invite, }) => ( - {getRoleTitle(invite.role, true)} address role + + The Manager address is used for: +
    +
  • Adding new keys
  • +
  • Removing existing keys
  • +
  • Adding extra bond amount
  • +
  • Claiming bond and rewards to the Rewards address
  • +
  • Covering locked bond
  • +
  • Proposing a new Manager address
  • +
+ + ) : ( + <> + The Rewards address is used for: +
    +
  • Claiming bond and rewards
  • +
  • Adding extra bond amount
  • +
  • Covering locked bond
  • +
  • Proposing a new Rewards address
  • +
  • + Resetting the Manager address to the current Rewards address +
  • +
+ + ) + } + > + {getRoleTitle(invite.role, true)} address role +
); diff --git a/shared/components/invite-content/style.ts b/shared/components/invite-content/style.ts index 4e5bcce4..f2e2dc9f 100644 --- a/shared/components/invite-content/style.ts +++ b/shared/components/invite-content/style.ts @@ -1,4 +1,3 @@ -import { BadgeStyle } from 'shared/node-operator/role-badge/styles'; import styled from 'styled-components'; export const InviteContentStyle = styled.div` @@ -11,6 +10,16 @@ export const InviteContentStyle = styled.div` color: var(--lido-color-text); `; -export const Badge = styled(BadgeStyle).attrs({ $background: 'dark' })` - text-transform: unset; +export const Badge = styled.span` + border-radius: ${({ theme }) => theme.borderRadiusesMap.md}px; + font-size: ${({ theme }) => theme.fontSizesMap.xs}px; + + display: inline-flex; + justify-content: center; + align-items: center; + padding: 2px 8px; + + background: var(--lido-color-background); + background: var(--lido-color-shadowLight); + color: var(--lido-color-textSecondary); `; diff --git a/styles/global.ts b/styles/global.ts index 1e3fe5ff..6d7a8beb 100644 --- a/styles/global.ts +++ b/styles/global.ts @@ -45,7 +45,7 @@ const GlobalStyle = createGlobalStyle` } ul { - padding-inline-start: 22px; + padding-inline-start: 1.8em; } ol { From bf6c347a253b0f5e3f4bb6f04ceda5e436df0efa Mon Sep 17 00:00:00 2001 From: exromany Date: Wed, 26 Mar 2025 23:16:52 +0700 Subject: [PATCH 38/58] chore: hoodi external dashboards --- consts/external-links.ts | 8 +- features/dashboard/dashboard.tsx | 6 +- .../dashboard/external/external-section.tsx | 72 ++--------------- .../external/use-external-button.tsx | 77 +++++++++++++++++++ shared/hooks/use-external-links.ts | 9 ++- 5 files changed, 94 insertions(+), 78 deletions(-) create mode 100644 features/dashboard/external/use-external-button.tsx diff --git a/consts/external-links.ts b/consts/external-links.ts index a97bc430..9b502725 100644 --- a/consts/external-links.ts +++ b/consts/external-links.ts @@ -90,13 +90,13 @@ export const EXTERNAL_LINKS_BY_NETWORK: Record = feedbackForm: 'https://forms.gle/ZBUqbykaZokJLf4M7', stakeWidget: 'https://stake-hoodi.testnet.fi', - feesMonitoring: 'https://fees-monitoring-hoodi.testnet.fi', + feesMonitoring: '', operatorsWidget: 'https://operators-hoodi.testnet.fi', beaconchain: 'https://hoodi.beaconcha.in', - beaconchainDashboard: '', - ratedExplorer: 'https://explorer.rated.network', + beaconchainDashboard: 'https://v2-beta-hoodi.beaconcha.in/dashboard', + ratedExplorer: '', subscribeEvents: 'https://docs.lido.fi/staking-modules/csm/guides/events', - keysApi: 'http://hr6vb81d1ndsx-hoodi-keys-api.valset-01.testnet.fi', + keysApi: 'https://keys-api-hoodi.testnet.fi', surveyApi: 'https://csm-surveys-api-testnet.up.railway.app', }, }; diff --git a/features/dashboard/dashboard.tsx b/features/dashboard/dashboard.tsx index 8abdeb5d..13dc26b1 100644 --- a/features/dashboard/dashboard.tsx +++ b/features/dashboard/dashboard.tsx @@ -3,12 +3,8 @@ import { BondSection } from './bond'; import { KeysSection } from './keys'; import { RolesSection } from './roles'; import { ExternalSection } from './external'; -import { getConfig } from 'config'; -import { CHAINS } from 'consts/chains'; import { SurveysCta } from './surveys-cta'; -const { defaultChain } = getConfig(); - export const Dashboard: FC = () => { return ( <> @@ -16,7 +12,7 @@ export const Dashboard: FC = () => { - {defaultChain !== CHAINS.Hoodi && } + ); }; diff --git a/features/dashboard/external/external-section.tsx b/features/dashboard/external/external-section.tsx index 51724c2c..dd4e8fc9 100644 --- a/features/dashboard/external/external-section.tsx +++ b/features/dashboard/external/external-section.tsx @@ -2,26 +2,12 @@ import { Accordion, Text } from '@lidofinance/lido-ui'; import { FC } from 'react'; import { Stack } from 'shared/components'; import { ExternalButtonLink } from './external-button-link'; - -import { ReactComponent as BeaconchaIcon } from 'assets/icons/beaconcha.svg'; -import { ReactComponent as RatedIcon } from 'assets/icons/rated.svg'; -import { ReactComponent as EthseerIcon } from 'assets/icons/ethseer.svg'; -import { ReactComponent as LidoIcon } from 'assets/icons/lido.svg'; -import { MATOMO_CLICK_EVENTS_TYPES } from 'consts/matomo-click-events'; -import { - useBeaconchainDashboardLink, - useEthSeerLink, - useFeesMonitoningLink, - useOperatorPortalLink, - useRatedLink, -} from 'shared/hooks'; +import { useExternalButtons } from './use-external-button'; export const ExternalSection: FC = () => { - const beaconchainDashboardLink = useBeaconchainDashboardLink(); - const feesMonitoningLink = useFeesMonitoningLink(); - const operatorPortalLink = useOperatorPortalLink(); - const ratedLink = useRatedLink(); - const ethSeerLink = useEthSeerLink(); + const buttons = useExternalButtons(); + + if (buttons.length === 0) return null; return ( { } > - } - href={beaconchainDashboardLink} - matomoEvent={MATOMO_CLICK_EVENTS_TYPES.dashboardExternalBeaconchaLink} - > - Dashboard displays statistics of your validators (up to 20 in free - plan) - - } - href={ratedLink} - matomoEvent={MATOMO_CLICK_EVENTS_TYPES.dashboardExternalRatedLink} - > - Provides effectiveness ratings, APRs and other useful metrics - - {ethSeerLink && ( - } - href={ethSeerLink} - matomoEvent={MATOMO_CLICK_EVENTS_TYPES.dashboardExternalEthSeerLink} - > - Provides real-time statistics of your validators’ performance - - )} - } - href={operatorPortalLink} - matomoEvent={ - MATOMO_CLICK_EVENTS_TYPES.dashboardExternalOperatorsPortalLink - } - > - Shows details about invalid keys - - } - href={feesMonitoningLink} - matomoEvent={ - MATOMO_CLICK_EVENTS_TYPES.dashboardExternalFeesMonitoringLink - } - > - Tracks missed slots and blocks with incorrect fee recipient/MEV relays - + {buttons.map((button) => ( + + ))} ); diff --git a/features/dashboard/external/use-external-button.tsx b/features/dashboard/external/use-external-button.tsx new file mode 100644 index 00000000..45b275e6 --- /dev/null +++ b/features/dashboard/external/use-external-button.tsx @@ -0,0 +1,77 @@ +import { ComponentProps, useMemo } from 'react'; +import { ExternalButtonLink } from './external-button-link'; + +import { ReactComponent as BeaconchaIcon } from 'assets/icons/beaconcha.svg'; +import { ReactComponent as EthseerIcon } from 'assets/icons/ethseer.svg'; +import { ReactComponent as LidoIcon } from 'assets/icons/lido.svg'; +import { ReactComponent as RatedIcon } from 'assets/icons/rated.svg'; +import { MATOMO_CLICK_EVENTS_TYPES } from 'consts/matomo-click-events'; +import { + useBeaconchainDashboardLink, + useEthSeerLink, + useFeesMonitoningLink, + useOperatorPortalLink, + useRatedLink, +} from 'shared/hooks'; + +export const useExternalButtons = () => { + const beaconchainDashboardLink = useBeaconchainDashboardLink(); + const feesMonitoningLink = useFeesMonitoningLink(); + const operatorPortalLink = useOperatorPortalLink(); + const ratedLink = useRatedLink(); + const ethSeerLink = useEthSeerLink(); + + return useMemo( + (): ComponentProps[] => + [ + beaconchainDashboardLink && { + title: 'beaconcha.in v2', + icon: , + href: beaconchainDashboardLink, + matomoEvent: MATOMO_CLICK_EVENTS_TYPES.dashboardExternalBeaconchaLink, + children: + 'Dashboard displays statistics of your validators (up to 20 in free plan)', + }, + ratedLink && { + title: 'Rated explorer', + icon: , + href: ratedLink, + matomoEvent: MATOMO_CLICK_EVENTS_TYPES.dashboardExternalRatedLink, + children: + 'Provides effectiveness ratings, APRs and other useful metrics', + }, + ethSeerLink && { + title: 'EthSeer', + icon: , + href: ethSeerLink, + matomoEvent: MATOMO_CLICK_EVENTS_TYPES.dashboardExternalEthSeerLink, + children: + 'Provides real-time statistics of your validators’ performance', + }, + operatorPortalLink && { + title: 'Lido Operators Portal', + icon: , + href: operatorPortalLink, + matomoEvent: + MATOMO_CLICK_EVENTS_TYPES.dashboardExternalOperatorsPortalLink, + children: 'Shows details about invalid keys', + }, + feesMonitoningLink && { + title: 'Lido Fees monitoring', + icon: , + href: feesMonitoningLink, + matomoEvent: + MATOMO_CLICK_EVENTS_TYPES.dashboardExternalFeesMonitoringLink, + children: + 'Tracks missed slots and blocks with incorrect fee recipient/MEV relays', + }, + ].filter((n) => !!n), + [ + beaconchainDashboardLink, + ethSeerLink, + feesMonitoningLink, + operatorPortalLink, + ratedLink, + ], + ); +}; diff --git a/shared/hooks/use-external-links.ts b/shared/hooks/use-external-links.ts index 6a0c7465..5d13f381 100644 --- a/shared/hooks/use-external-links.ts +++ b/shared/hooks/use-external-links.ts @@ -29,26 +29,27 @@ export const useBeaconchainDashboardLink = (directKeys?: string[]) => { export const useFeesMonitoningLink = () => { const nodeOperatorId = useNodeOperatorId(); const { stakingModuleId } = getCsmConstants(); + if (!links.feesMonitoring) return null; return `${links.feesMonitoring}/operatorInfo?stakingModuleIndex=${stakingModuleId}&operatorIndex=${nodeOperatorId}`; }; export const useOperatorPortalLink = () => { const nodeOperatorId = useNodeOperatorId(); const { stakingModuleId } = getCsmConstants(); + if (!links.operatorsWidget) return null; return `${links.operatorsWidget}/module/${stakingModuleId}/${nodeOperatorId}`; }; export const useRatedLink = () => { const nodeOperatorId = useNodeOperatorId(); const network = defaultChain === CHAINS.Mainnet ? 'mainnet' : 'hoodi'; + if (!links.ratedExplorer) return null; return `${links.ratedExplorer}/o/CSM%20Operator%20${nodeOperatorId}%20-%20Lido%20Community%20Staking%20Module?network=${network}`; }; export const useEthSeerLink = () => { const nodeOperatorId = useNodeOperatorId(); const network = defaultChain === CHAINS.Mainnet ? 'mainnet' : 'hoodi'; - if (links.ethseerDashboard) { - return `${links.ethseerDashboard}/entity/csm_operator${nodeOperatorId}_lido?network=${network}`; - } - return null; + if (!links.ethseerDashboard) return null; + return `${links.ethseerDashboard}/entity/csm_operator${nodeOperatorId}_lido?network=${network}`; }; From 15b1a407b81e1c6ae7886c9d551128f5d52354ad Mon Sep 17 00:00:00 2001 From: exromany Date: Wed, 26 Mar 2025 23:56:31 +0700 Subject: [PATCH 39/58] fix: append with correct type --- .../context/use-accept-invite-submit.ts | 2 +- .../submit-keys-form/context/use-submit-keys-submit.ts | 7 +++++-- .../node-operator-provider/node-operator-provider.tsx | 8 ++++---- providers/node-operator-provider/use-appen-and-switch.ts | 6 +++--- .../node-operator-provider/use-node-operators-list.ts | 8 ++++---- 5 files changed, 17 insertions(+), 14 deletions(-) diff --git a/features/accept-invite/accept-invite-form/context/use-accept-invite-submit.ts b/features/accept-invite/accept-invite-form/context/use-accept-invite-submit.ts index e742e68c..3428ed1f 100644 --- a/features/accept-invite/accept-invite-form/context/use-accept-invite-submit.ts +++ b/features/accept-invite/accept-invite-form/context/use-accept-invite-submit.ts @@ -95,7 +95,7 @@ export const useAcceptInviteSubmit = ({ txModalStages.success({ ...invite, address }, txHash); // TODO: move to onConfirm - appendNO(invite); + appendNO({ id: invite.id, roles: [invite.role] }); return true; } catch (error) { diff --git a/features/create-node-operator/submit-keys-form/context/use-submit-keys-submit.ts b/features/create-node-operator/submit-keys-form/context/use-submit-keys-submit.ts index 0a171f81..52235eb9 100644 --- a/features/create-node-operator/submit-keys-form/context/use-submit-keys-submit.ts +++ b/features/create-node-operator/submit-keys-form/context/use-submit-keys-submit.ts @@ -20,6 +20,7 @@ import { addressOrZero, formatKeys, getAddedNodeOperator, + packRoles, runWithTransactionLogger, } from 'utils'; import { Address } from 'wagmi'; @@ -216,8 +217,10 @@ export const useSubmitKeysSubmit = ({ if (nodeOperator) { appendNO({ id: nodeOperator.id, - manager: isUserOrZero(nodeOperator.managerAddress), - rewards: isUserOrZero(nodeOperator.rewardsAddress), + roles: packRoles({ + rewards: isUserOrZero(nodeOperator.rewardsAddress), + manager: isUserOrZero(nodeOperator.managerAddress), + }), }); } diff --git a/providers/node-operator-provider/node-operator-provider.tsx b/providers/node-operator-provider/node-operator-provider.tsx index f703b343..2a308b6b 100644 --- a/providers/node-operator-provider/node-operator-provider.tsx +++ b/providers/node-operator-provider/node-operator-provider.tsx @@ -6,17 +6,17 @@ import { useMemo, } from 'react'; import invariant from 'tiny-invariant'; -import { NodeOperator, NodeOperatorId, NodeOperatorRoles } from 'types'; +import { NodeOperator } from 'types'; +import { useAppendAndSwitch } from './use-appen-and-switch'; import { useGetActiveNodeOperator } from './use-get-active-node-operator'; import { useNodeOperatorsList } from './use-node-operators-list'; -import { useAppendAndSwitch } from './use-appen-and-switch'; export type NodeOperatorContextValue = { list: NodeOperator[]; isListLoading: boolean; - append: (nodeOperator: NodeOperatorRoles) => void; active?: NodeOperator; - switchActive: (id: NodeOperatorId) => void; + append: ReturnType['append']; + switchActive: ReturnType['switchActive']; appendAndSwitch: ReturnType; }; diff --git a/providers/node-operator-provider/use-appen-and-switch.ts b/providers/node-operator-provider/use-appen-and-switch.ts index 0d416504..2c3ca606 100644 --- a/providers/node-operator-provider/use-appen-and-switch.ts +++ b/providers/node-operator-provider/use-appen-and-switch.ts @@ -1,11 +1,11 @@ import { NodeOperatorStructOutput } from 'generated/CSModule'; import { useCallback } from 'react'; import { useAddressCompare, useCSModuleRPC } from 'shared/hooks'; -import { NodeOperator, NodeOperatorId, NodeOperatorRoles } from 'types'; +import { NodeOperator, NodeOperatorId } from 'types'; import { packRoles } from 'utils'; export const useAppendAndSwitch = ( - append: (nodeOperator: NodeOperatorRoles) => void, + append: (nodeOperator: NodeOperator) => void, setActive: (no: NodeOperator) => void, ) => { const contract = useCSModuleRPC(); @@ -21,7 +21,7 @@ export const useAppendAndSwitch = ( // TODO: fix for spectacular if (rewards || manager) { - append({ id, manager, rewards }); + append({ id, roles: packRoles({ rewards, manager }) }); setActive({ id, roles: packRoles({ rewards, manager }) }); } }, diff --git a/providers/node-operator-provider/use-node-operators-list.ts b/providers/node-operator-provider/use-node-operators-list.ts index 1d634028..e0aa5327 100644 --- a/providers/node-operator-provider/use-node-operators-list.ts +++ b/providers/node-operator-provider/use-node-operators-list.ts @@ -1,8 +1,8 @@ import { useCallback, useMemo } from 'react'; -import { NodeOperatorRoles } from 'types'; +import { useCsmNodeOperators } from 'shared/hooks'; +import { NodeOperator } from 'types'; import { mergeRoles } from 'utils'; import { useCachedNodeOperator } from './use-cached-node-operator'; -import { useCsmNodeOperators } from 'shared/hooks'; export const useNodeOperatorsList = () => { const { data, initialLoading, mutate } = useCsmNodeOperators(); @@ -10,9 +10,9 @@ export const useNodeOperatorsList = () => { const cached = useCachedNodeOperator(); const append = useCallback( - (income: NodeOperatorRoles) => { + (income: NodeOperator) => { // TODO: fix for spectacular - if (income.manager || income.rewards) { + if (income.roles.length > 0) { void mutate((prev = []) => [...mergeRoles(prev, income)]); } }, From be0aca1c96565d86738bbe778490ed9f8421a8ca Mon Sep 17 00:00:00 2001 From: exromany Date: Wed, 26 Mar 2025 23:57:16 +0700 Subject: [PATCH 40/58] feat: navigate to dashboard after last invite --- .../context/use-accept-invite-submit.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/features/accept-invite/accept-invite-form/context/use-accept-invite-submit.ts b/features/accept-invite/accept-invite-form/context/use-accept-invite-submit.ts index 3428ed1f..be799f8b 100644 --- a/features/accept-invite/accept-invite-form/context/use-accept-invite-submit.ts +++ b/features/accept-invite/accept-invite-form/context/use-accept-invite-submit.ts @@ -12,6 +12,8 @@ import { AcceptInviteFormInputType, AcceptInviteFormNetworkData, } from './types'; +import { useNavigate } from 'shared/navigate'; +import { PATH } from 'consts/urls'; // TODO: move to hooks type UseAcceptInviteOptions = { @@ -58,6 +60,7 @@ export const useAcceptInviteSubmit = ({ }: UseAcceptInviteOptions) => { const { txModalStages } = useTxModalStagesAcceptInvite(); const { append: appendNO } = useNodeOperatorContext(); + const n = useNavigate(); const getTx = useAcceptInviteTx(); const sendTx = useSendTx(); @@ -65,7 +68,7 @@ export const useAcceptInviteSubmit = ({ const acceptInvite = useCallback( async ( { invite }: AcceptInviteFormInputType, - { address }: AcceptInviteFormNetworkData, + { address, invites }: AcceptInviteFormNetworkData, ): Promise => { invariant(invite, 'Invite is not defined'); @@ -92,17 +95,20 @@ export const useAcceptInviteSubmit = ({ await onConfirm?.(); - txModalStages.success({ ...invite, address }, txHash); - // TODO: move to onConfirm appendNO({ id: invite.id, roles: [invite.role] }); + if (invites && invites.length <= 1) { + void n(PATH.HOME); + } + + txModalStages.success({ ...invite, address }, txHash); return true; } catch (error) { return handleTxError(error, txModalStages, onRetry); } }, - [getTx, txModalStages, onConfirm, appendNO, sendTx, onRetry], + [txModalStages, getTx, onConfirm, appendNO, n, sendTx, onRetry], ); return { From 48d9a00d46e318f177222bc6332c3dbeff84beb5 Mon Sep 17 00:00:00 2001 From: exromany Date: Thu, 27 Mar 2025 00:42:30 +0700 Subject: [PATCH 41/58] feat: success screen after propose address --- .../hooks/use-tx-modal-stages-change-role.tsx | 57 ++++++++++++++++--- shared/components/address/styles.tsx | 2 + .../after-address-proposed.tsx | 35 ++++++++++++ .../tx-stages-parts/index.ts | 1 + 4 files changed, 88 insertions(+), 7 deletions(-) create mode 100644 shared/transaction-modal/tx-stages-parts/after-address-proposed.tsx diff --git a/features/change-role/change-role-form/hooks/use-tx-modal-stages-change-role.tsx b/features/change-role/change-role-form/hooks/use-tx-modal-stages-change-role.tsx index 45724103..ed14ad42 100644 --- a/features/change-role/change-role-form/hooks/use-tx-modal-stages-change-role.tsx +++ b/features/change-role/change-role-form/hooks/use-tx-modal-stages-change-role.tsx @@ -1,7 +1,10 @@ +import { Text } from '@lidofinance/lido-ui'; import { ROLES } from 'consts/roles'; import { capitalize } from 'lodash'; +import { Address } from 'shared/components'; import { getRoleTitle } from 'shared/node-operator'; import { + AfterAddressProposed, TransactionModalTransitStage, TxStagePending, TxStageSign, @@ -20,38 +23,78 @@ type Props = { isRevoke: boolean; }; -// TODO: show address with
component const getTexts = (props: Props) => { return props.isManagerReset || props.isRewardsChange ? { sign: { title: `You are changing ${getRoleTitle(props.role)} address`, - description: `New address ${props.address}`, + description: ( + <> + New ${getRoleTitle(props.role)} address is{' '} + +
+ + + ), }, success: { title: `${capitalize(getRoleTitle(props.role))} address has been changed`, - description: `New address ${props.address}`, + description: ( + <> + New ${getRoleTitle(props.role)} address is{' '} + +
+ + + ), }, } : props.isRevoke ? { sign: { title: `You are revoking request for ${getRoleTitle(props.role)} address change`, - description: `Address stays ${props.currentAddress}`, + description: ( + <> + Address stays{' '} + +
+ + + ), }, success: { title: `Proposed request for ${getRoleTitle(props.role)} address has been revoked`, - description: `Address stays ${props.currentAddress}`, + description: ( + <> + Address stays{' '} + +
+ + + ), }, } : { sign: { title: `You are proposing ${getRoleTitle(props.role)} address change`, - description: `Proposed address ${props.address}`, + description: ( + <> + Proposed address{' '} + +
+ + + ), }, success: { title: `New ${getRoleTitle(props.role)} address has been proposed`, - description: `To complete the address change, the owner of the new address must confirm the change`, + description: ( + <> +
+
+ + + ), }, }; }; diff --git a/shared/components/address/styles.tsx b/shared/components/address/styles.tsx index 7ee36ad5..7f62a0c3 100644 --- a/shared/components/address/styles.tsx +++ b/shared/components/address/styles.tsx @@ -15,6 +15,8 @@ export const AddressContainerStyle = styled(StackStyle).attrs({ $gap: 'xs', $align: 'center', })<{ $bold?: boolean }>` + display: inline-flex; + ${LinkStyled} { svg { display: inline-flex; diff --git a/shared/transaction-modal/tx-stages-parts/after-address-proposed.tsx b/shared/transaction-modal/tx-stages-parts/after-address-proposed.tsx new file mode 100644 index 00000000..881ea727 --- /dev/null +++ b/shared/transaction-modal/tx-stages-parts/after-address-proposed.tsx @@ -0,0 +1,35 @@ +import { Text } from '@lidofinance/lido-ui'; +import { FC } from 'react'; +import { Address } from 'shared/components'; +import styled from 'styled-components'; + +type Props = { + address: string; +}; + +export const AfterAddressProposed: FC = ({ address }) => { + return ( + + What is next: +
+
    +
  1. + Connect to CSM UI with the proposed address + +
    + +
  2. +
  3. Go to Roles tab → Inbox requests to confirm the change
  4. +
+
+ ); +}; + +const BlockStyled = styled.div` + text-align: left; + line-height: 24px; + + background-color: var(--lido-color-backgroundSecondary); + border-radius: ${({ theme }) => theme.borderRadiusesMap.lg}px; + padding: ${({ theme }) => theme.spaceMap.md}px; +`; diff --git a/shared/transaction-modal/tx-stages-parts/index.ts b/shared/transaction-modal/tx-stages-parts/index.ts index 62fece23..95c8f5b5 100644 --- a/shared/transaction-modal/tx-stages-parts/index.ts +++ b/shared/transaction-modal/tx-stages-parts/index.ts @@ -1,3 +1,4 @@ +export * from './after-address-proposed'; export * from './after-keys-upload'; export * from './success-text'; export * from './tx-amount'; From da547cabfc4d96388fb72d1013af37d74289c55d Mon Sep 17 00:00:00 2001 From: exromany Date: Thu, 27 Mar 2025 00:43:00 +0700 Subject: [PATCH 42/58] feat: action required text after proposed address --- .../change-role-form/controls/info.tsx | 53 +++++++++++++------ 1 file changed, 36 insertions(+), 17 deletions(-) diff --git a/features/change-role/change-role-form/controls/info.tsx b/features/change-role/change-role-form/controls/info.tsx index 87edf5ec..7ead48fc 100644 --- a/features/change-role/change-role-form/controls/info.tsx +++ b/features/change-role/change-role-form/controls/info.tsx @@ -1,9 +1,10 @@ import { FC, useCallback } from 'react'; import { useFormContext } from 'react-hook-form'; -import { Latice, TitledAddress, Warning } from 'shared/components'; +import { Latice, Stack, TitledAddress, Warning } from 'shared/components'; import { SubmitButtonHookForm } from 'shared/hook-form/controls'; import { ChangeRoleFormInputType, useChangeRoleFormData } from '../context'; import { useRole } from '../hooks/use-role'; +import { Text } from '@lidofinance/lido-ui'; export const Info: FC = () => { const role = useRole(); @@ -22,24 +23,42 @@ export const Info: FC = () => { title={`Current ${role} address`} address={currentAddress} /> - + + + {isPropose && ( + + Revoke + + )} + + } + address={proposedAddress} + /> + {proposedAddress && ( <> - - {isPropose && ( - - Revoke - - )} + + Action required + + +
    +
  1. Connect to CSM UI with the proposed address
  2. +
  3. + Go to Roles tab → Inbox requests to confirm the change +
  4. +
+
- } - address={proposedAddress} - /> + )} + ); From 46c00b38d01fe3f1361f340bda065e7b8942a6d0 Mon Sep 17 00:00:00 2001 From: exromany Date: Thu, 27 Mar 2025 15:05:17 +0700 Subject: [PATCH 43/58] feat: redirect to keys view after add keys --- .../add-keys/add-keys/context/use-add-keys-submit.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/features/add-keys/add-keys/context/use-add-keys-submit.ts b/features/add-keys/add-keys/context/use-add-keys-submit.ts index 175ad2dc..08fd1f27 100644 --- a/features/add-keys/add-keys/context/use-add-keys-submit.ts +++ b/features/add-keys/add-keys/context/use-add-keys-submit.ts @@ -15,6 +15,8 @@ import { NodeOperatorId } from 'types'; import { addExtraWei, formatKeys, runWithTransactionLogger } from 'utils'; import { useTxModalStagesAddKeys } from '../hooks/use-tx-modal-stages-add-keys'; import { AddKeysFormInputType, AddKeysFormNetworkData } from './types'; +import { useNavigate } from 'shared/navigate'; +import { PATH } from 'consts/urls'; type AddKeysOptions = { onConfirm?: () => Promise | void; @@ -86,6 +88,7 @@ export const useAddKeysSubmit = ({ onConfirm, onRetry }: AddKeysOptions) => { const getPermitOrApprove = usePermitOrApprove(); const getTx = useAddKeysTx(); const sendTx = useSendTx(); + const n = useNavigate(); const { addCacheKeys } = useKeysCache(); @@ -138,14 +141,16 @@ export const useAddKeysSubmit = ({ onConfirm, onRetry }: AddKeysOptions) => { await onConfirm?.(); + // TODO: move to onConfirm + void addCacheKeys(depositData.map(({ pubkey }) => pubkey)); + + void n(PATH.KEYS_VIEW); + txModalStages.success( { keys: depositData.map((key) => key.pubkey) }, txHash, ); - // TODO: move to onConfirm - void addCacheKeys(depositData.map(({ pubkey }) => pubkey)); - return true; } catch (error) { return handleTxError(error, txModalStages, onRetry); @@ -157,6 +162,7 @@ export const useAddKeysSubmit = ({ onConfirm, onRetry }: AddKeysOptions) => { getTx, onConfirm, addCacheKeys, + n, sendTx, onRetry, ], From 2e0cbe31d4035221c20763af5c4cbfdedb811e65 Mon Sep 17 00:00:00 2001 From: exromany Date: Thu, 27 Mar 2025 15:52:47 +0700 Subject: [PATCH 44/58] feat: add success screen after create NO with custom addresses --- consts/matomo-click-events.ts | 6 ++ .../context/use-submit-keys-submit.ts | 4 + .../hooks/use-tx-modal-stages-submit-keys.tsx | 21 ++++- .../after-create-custom-node-operator.tsx | 90 +++++++++++++++++++ .../tx-stages-parts/after-keys-upload.tsx | 4 +- .../tx-stages-parts/index.ts | 1 + shared/wallet/disconnect/disconnect.tsx | 32 +++++++ shared/wallet/index.ts | 1 + shared/wallet/wallet-modal/styles.tsx | 6 +- shared/wallet/wallet-modal/wallet-modal.tsx | 4 +- 10 files changed, 156 insertions(+), 13 deletions(-) create mode 100644 shared/transaction-modal/tx-stages-parts/after-create-custom-node-operator.tsx create mode 100644 shared/wallet/disconnect/disconnect.tsx diff --git a/consts/matomo-click-events.ts b/consts/matomo-click-events.ts index 312931b5..a2527e88 100644 --- a/consts/matomo-click-events.ts +++ b/consts/matomo-click-events.ts @@ -10,6 +10,7 @@ export const prefixed = (template: TemplateStringsArray, ...args: string[]) => { export const enum MATOMO_CLICK_EVENTS_TYPES { // Welcome connectWallet = 'connectWallet', + disconnectWallet = 'disconnectWallet', connectAsNodeOperator = 'connectAsNodeOperator', connectToBecomeNodeOperator = 'connectToBecomeNodeOperator', welcomeDetailedLink = 'welcomeDetailedLink', @@ -103,6 +104,11 @@ export const MATOMO_CLICK_EVENTS: Record< 'Push «Connect wallet» button', prefixed`connect_wallet`, ], + [MATOMO_CLICK_EVENTS_TYPES.disconnectWallet]: [ + MATOMO_APP_NAME, + 'Push «Disonnect» button', + prefixed`disconnect_wallet`, + ], [MATOMO_CLICK_EVENTS_TYPES.connectAsNodeOperator]: [ MATOMO_APP_NAME, 'Push «I am a Node Operator» on Welcome screen', diff --git a/features/create-node-operator/submit-keys-form/context/use-submit-keys-submit.ts b/features/create-node-operator/submit-keys-form/context/use-submit-keys-submit.ts index 52235eb9..89893bc4 100644 --- a/features/create-node-operator/submit-keys-form/context/use-submit-keys-submit.ts +++ b/features/create-node-operator/submit-keys-form/context/use-submit-keys-submit.ts @@ -205,6 +205,10 @@ export const useSubmitKeysSubmit = ({ { nodeOperatorId: nodeOperator?.id, keys: depositData.map((key) => key.pubkey), + roles: packRoles({ + manager: isUserOrZero(nodeOperator?.managerAddress), + rewards: isUserOrZero(nodeOperator?.rewardsAddress), + }), }, txHash, ); diff --git a/features/create-node-operator/submit-keys-form/hooks/use-tx-modal-stages-submit-keys.tsx b/features/create-node-operator/submit-keys-form/hooks/use-tx-modal-stages-submit-keys.tsx index 2ea4e96c..2ad1e87e 100644 --- a/features/create-node-operator/submit-keys-form/hooks/use-tx-modal-stages-submit-keys.tsx +++ b/features/create-node-operator/submit-keys-form/hooks/use-tx-modal-stages-submit-keys.tsx @@ -7,13 +7,18 @@ import { import { TOKENS } from 'consts/tokens'; import type { BigNumber } from 'ethers'; import { Plural } from 'shared/components'; -import { AfterKeysUpload, TxAmount } from 'shared/transaction-modal'; +import { + AfterCreateCustomNodeOperator, + AfterKeysUpload, + TxAmount, +} from 'shared/transaction-modal'; import { TxStagePending, TxStageSign, TxStageSuccess, } from 'shared/transaction-modal/tx-stages-basic'; import { NodeOperatorId } from 'types'; +import { ROLES } from 'consts/roles'; type Props = { keysCount: number; @@ -21,7 +26,11 @@ type Props = { token: TOKENS; }; -type SuccessProps = { nodeOperatorId?: NodeOperatorId; keys: string[] }; +type SuccessProps = { + nodeOperatorId?: NodeOperatorId; + keys: string[]; + roles: ROLES[]; +}; const getTxModalStagesSubmitKeys = ( transitStage: TransactionModalTransitStage, @@ -67,7 +76,7 @@ const getTxModalStagesSubmitKeys = ( />, ), - success: ({ nodeOperatorId, keys }: SuccessProps, txHash?: string) => { + success: ({ nodeOperatorId, keys, roles }: SuccessProps, txHash?: string) => { return transitStage( {nodeOperatorId}

- + {roles.length > 0 ? ( + + ) : ( + + )} ) : undefined } diff --git a/shared/transaction-modal/tx-stages-parts/after-create-custom-node-operator.tsx b/shared/transaction-modal/tx-stages-parts/after-create-custom-node-operator.tsx new file mode 100644 index 00000000..e5daa7a4 --- /dev/null +++ b/shared/transaction-modal/tx-stages-parts/after-create-custom-node-operator.tsx @@ -0,0 +1,90 @@ +import { getExternalLinks } from 'consts/external-links'; +import { MATOMO_CLICK_EVENTS_TYPES } from 'consts/matomo-click-events'; +import { useModalActions } from 'providers/modal-provider'; +import { FC } from 'react'; +import { MatomoLink } from 'shared/components'; +import { useBeaconchainDashboardLink } from 'shared/hooks'; +import { Disconnect } from 'shared/wallet'; +import styled from 'styled-components'; +import { NodeOperatorId } from 'types'; + +type Props = { + nodeOperatorId?: NodeOperatorId; + keys: string[]; +}; + +export const AfterCreateCustomNodeOperator: FC = ({ keys }) => { + const beaconchainDashboardLink = useBeaconchainDashboardLink(keys); + const { subscribeEvents, beaconchain } = getExternalLinks(); + const { closeModal } = useModalActions(); + + return ( + <> + + What is next: +
+
    +
  1. + Connect to CSM UI with the address you specified as Reward/Manager + Address +
  2. +
  3. Wait for your keys to be deposited to through the protocol.
  4. +
  5. + Once your keys become active ( + {beaconchain && ( + <> + you can check their statuses on{' '} + + beaconcha.in + {' '} + or{' '} + + )} + subscribe to the{' '} + + CSM events notifications + + ) make sure your validators are producing attestations{' '} + {beaconchainDashboardLink && ( + <> + (you can use the{' '} + + beaconcha.in dashboard + {' '} + to check) + + )} +
  6. +
+
+
+ closeModal()} fullwidth> + Disconnect wallet + + + ); +}; + +const BlockStyled = styled.div` + text-align: left; + line-height: 24px; + + background-color: var(--lido-color-backgroundSecondary); + border-radius: ${({ theme }) => theme.borderRadiusesMap.lg}px; + padding: ${({ theme }) => theme.spaceMap.md}px; +`; diff --git a/shared/transaction-modal/tx-stages-parts/after-keys-upload.tsx b/shared/transaction-modal/tx-stages-parts/after-keys-upload.tsx index e98b1e9a..dda6592b 100644 --- a/shared/transaction-modal/tx-stages-parts/after-keys-upload.tsx +++ b/shared/transaction-modal/tx-stages-parts/after-keys-upload.tsx @@ -23,7 +23,7 @@ export const AfterKeysUpload: FC = ({ keys }) => {
  1. Wait for your keys to be deposited to through the protocol.
  2. - Once your keys become active (check the status on the{' '} + Once your keys become active (you can check their statuses on the{' '} = ({ keys }) => { ) make sure your validators are producing attestations{' '} {beaconchainDashboardLink && ( <> - (you can use{' '} + (you can use the{' '} > +> = ({ + children, + matomoEvent = MATOMO_CLICK_EVENTS_TYPES.disconnectWallet, + onClick, + ...rest +}) => { + const { disconnect } = useDisconnect(); + + const handleClick: MouseEventHandler = useCallback( + (e) => { + trackMatomoEvent(matomoEvent); + disconnect?.(); + onClick?.(e); + }, + [disconnect, matomoEvent, onClick], + ); + + return ( + + ); +}; diff --git a/shared/wallet/index.ts b/shared/wallet/index.ts index 1084723c..150c2a03 100644 --- a/shared/wallet/index.ts +++ b/shared/wallet/index.ts @@ -1,4 +1,5 @@ export { Button } from './button/button'; export { Connect } from './connect/connect'; +export { Disconnect } from './disconnect/disconnect'; export * from './wallet-modal'; export * from './fallback'; diff --git a/shared/wallet/wallet-modal/styles.tsx b/shared/wallet/wallet-modal/styles.tsx index 93f1f2c1..cec0dd2c 100644 --- a/shared/wallet/wallet-modal/styles.tsx +++ b/shared/wallet/wallet-modal/styles.tsx @@ -1,5 +1,5 @@ -import { Button } from '@lidofinance/lido-ui'; import styled from 'styled-components'; +import { Disconnect } from '../disconnect/disconnect'; export const WalletModalContentStyle = styled.div` background-color: var(--lido-color-background); @@ -22,9 +22,7 @@ export const WalletModalConnectorStyle = styled.div` margin-right: auto; `; -export const WalletModalDisconnectStyle = styled((props) => ( - - - - - - ); -}; diff --git a/shared/hooks/index.ts b/shared/hooks/index.ts index 01cdd3c7..c5fc25b6 100644 --- a/shared/hooks/index.ts +++ b/shared/hooks/index.ts @@ -1,7 +1,6 @@ export * from './use-account'; export * from './use-address-compare'; export * from './use-approve'; -export * from './use-ask-how-did-you-learn-csm'; export * from './use-await-network-data'; export * from './use-awaiter'; export * from './use-can-create-node-operator'; diff --git a/shared/hooks/use-ask-how-did-you-learn-csm.tsx b/shared/hooks/use-ask-how-did-you-learn-csm.tsx deleted file mode 100644 index 539f8fa7..00000000 --- a/shared/hooks/use-ask-how-did-you-learn-csm.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { useLocalStorage } from '@lido-sdk/react'; -import { useMemo } from 'react'; - -export const useAskHowDidYouLearnCsm = () => { - const [state, setState] = useLocalStorage< - 'ask' | 'answered' | 'closed' | undefined - >(`ask-how-learn-csm`, undefined); - - return useMemo( - () => ({ - canAsk: state === 'ask', - ask: () => !state && setState('ask'), - answer: () => setState('answered'), - rejectAnswer: () => state !== 'answered' && setState('closed'), - }), - [setState, state], - ); -}; diff --git a/utils/get-added-node-operator.ts b/utils/get-added-node-operator.ts index d2b3a146..cb834089 100644 --- a/utils/get-added-node-operator.ts +++ b/utils/get-added-node-operator.ts @@ -19,8 +19,8 @@ export const getAddedNodeOperator = (receipt: ContractReceipt) => { const event = int.parseLog(log) as any as NodeOperatorAddedEvent; return { id: getNodeOperatorIdFromEvent(event), - managerAddress: event.args.managerAddress, - rewardsAddress: event.args.rewardAddress, + manager: event.args.managerAddress, + rewards: event.args.rewardAddress, }; } return undefined; From 9a77d66b5fdd0921382f6899376b958cc448ff47 Mon Sep 17 00:00:00 2001 From: exromany Date: Thu, 27 Mar 2025 17:22:13 +0700 Subject: [PATCH 46/58] feat: show banner after create NO with custom addresses --- .../context/use-submit-keys-submit.ts | 25 +++++++++++-------- .../banner-operator-custom-addresses.tsx | 23 +++++++++++++++++ .../banner-operator-custom-addresses/index.ts | 2 ++ .../use-operator-custom-addresses.ts | 9 +++++++ features/starter-pack/starter-pack.tsx | 2 ++ 5 files changed, 50 insertions(+), 11 deletions(-) create mode 100644 features/starter-pack/banner-operator-custom-addresses/banner-operator-custom-addresses.tsx create mode 100644 features/starter-pack/banner-operator-custom-addresses/index.ts create mode 100644 features/starter-pack/banner-operator-custom-addresses/use-operator-custom-addresses.ts diff --git a/features/create-node-operator/submit-keys-form/context/use-submit-keys-submit.ts b/features/create-node-operator/submit-keys-form/context/use-submit-keys-submit.ts index f18a9f48..76531961 100644 --- a/features/create-node-operator/submit-keys-form/context/use-submit-keys-submit.ts +++ b/features/create-node-operator/submit-keys-form/context/use-submit-keys-submit.ts @@ -28,6 +28,7 @@ import { useTxModalStagesSubmitKeys } from '../hooks/use-tx-modal-stages-submit- import { SubmitKeysFormInputType, SubmitKeysFormNetworkData } from './types'; import { PATH } from 'consts/urls'; import { useNavigate } from 'shared/navigate'; +import { useOperatorCustomAddresses } from 'features/starter-pack/banner-operator-custom-addresses'; type SubmitKeysOptions = { onConfirm?: () => Promise | void; @@ -127,6 +128,7 @@ export const useSubmitKeysSubmit = ({ const isUserOrZero = useAddressCompare(true); const { addCacheKeys } = useKeysCache(); const n = useNavigate(); + const [, setOperatorCustomAddresses] = useOperatorCustomAddresses(); const confirmCustomAddresses = useConfirmCustomAddressesModal(); @@ -211,16 +213,16 @@ export const useSubmitKeysSubmit = ({ // TODO: move to onConfirm void addCacheKeys(depositData.map(({ pubkey }) => pubkey)); - // TODO: move to onConfirm if (nodeOperator?.id) { - appendNO({ - id: nodeOperator.id, - roles, - }); - } - - if (roles.length === 0) { - void n(PATH.HOME); + if (roles.length === 0) { + setOperatorCustomAddresses(nodeOperator.id); + void n(PATH.HOME); + } else { + appendNO({ + id: nodeOperator.id, + roles, + }); + } } txModalStages.success( @@ -243,11 +245,12 @@ export const useSubmitKeysSubmit = ({ txModalStages, getTx, isUserOrZero, - addCacheKeys, onConfirm, + addCacheKeys, sendTx, - appendNO, + setOperatorCustomAddresses, n, + appendNO, onRetry, ], ); diff --git a/features/starter-pack/banner-operator-custom-addresses/banner-operator-custom-addresses.tsx b/features/starter-pack/banner-operator-custom-addresses/banner-operator-custom-addresses.tsx new file mode 100644 index 00000000..cbf14477 --- /dev/null +++ b/features/starter-pack/banner-operator-custom-addresses/banner-operator-custom-addresses.tsx @@ -0,0 +1,23 @@ +import { FC } from 'react'; +import { Banner } from 'shared/components'; +import { DescriptorId } from 'shared/node-operator'; +import { useOperatorCustomAddresses } from './use-operator-custom-addresses'; + +export const BannerOperatorCustomAddresses: FC = () => { + const [nodeOperatorId] = useOperatorCustomAddresses(); + + if (!nodeOperatorId) { + return null; + } + + return ( + + To continue, connect to CSM UI with the address you specified as + Reward/Manager Address for or create + a new Node Operator using the currently connected address. + + ); +}; diff --git a/features/starter-pack/banner-operator-custom-addresses/index.ts b/features/starter-pack/banner-operator-custom-addresses/index.ts new file mode 100644 index 00000000..5465cb67 --- /dev/null +++ b/features/starter-pack/banner-operator-custom-addresses/index.ts @@ -0,0 +1,2 @@ +export * from './banner-operator-custom-addresses'; +export * from './use-operator-custom-addresses'; diff --git a/features/starter-pack/banner-operator-custom-addresses/use-operator-custom-addresses.ts b/features/starter-pack/banner-operator-custom-addresses/use-operator-custom-addresses.ts new file mode 100644 index 00000000..d48f296c --- /dev/null +++ b/features/starter-pack/banner-operator-custom-addresses/use-operator-custom-addresses.ts @@ -0,0 +1,9 @@ +import { useSessionStorage } from 'shared/hooks'; +import { NodeOperatorId } from 'types'; + +export const useOperatorCustomAddresses = () => { + return useSessionStorage( + `operator-custom-address`, + undefined, + ); +}; diff --git a/features/starter-pack/starter-pack.tsx b/features/starter-pack/starter-pack.tsx index 0649e5dd..cbfd3e41 100644 --- a/features/starter-pack/starter-pack.tsx +++ b/features/starter-pack/starter-pack.tsx @@ -8,6 +8,7 @@ import { Faq } from 'shared/components'; import { useCsmEarlyAdoption } from 'shared/hooks'; import { useCsmPaused, useCsmPublicRelease } from 'shared/hooks/useCsmStatus'; import { trackMatomoEvent } from 'utils'; +import { BannerOperatorCustomAddresses } from './banner-operator-custom-addresses'; import { ConsumedBanner } from './consumed-banner'; import { NotEligibleBanner } from './not-eligible-banner/not-eligible-banner'; import { PausedBanner } from './paused-banner'; @@ -44,6 +45,7 @@ export const StarterPack: FC = () => { return ( <> + {content} From fec63347e969299236170b12ce5c4f187adfef98 Mon Sep 17 00:00:00 2001 From: exromany Date: Thu, 27 Mar 2025 18:56:12 +0700 Subject: [PATCH 47/58] feat: claim - title on submit button --- .../controls/submit-button.tsx | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/features/claim-bond/claim-bond-form/controls/submit-button.tsx b/features/claim-bond/claim-bond-form/controls/submit-button.tsx index 82909fe4..23752044 100644 --- a/features/claim-bond/claim-bond-form/controls/submit-button.tsx +++ b/features/claim-bond/claim-bond-form/controls/submit-button.tsx @@ -1,22 +1,32 @@ +import { TOKENS } from 'consts/tokens'; +import { useWatch } from 'react-hook-form'; import { PausedButton, SubmitButtonHookForm } from 'shared/hook-form/controls'; import { ClaimBondFormInputType, useClaimBondFormData } from '../context'; -import { useWatch } from 'react-hook-form'; export const SubmitButton = () => { - const claimRewards = useWatch({ - name: 'claimRewards', + const [claimRewards, token, amount] = useWatch< + ClaimBondFormInputType, + ['claimRewards', 'token', 'amount'] + >({ + name: ['claimRewards', 'token', 'amount'], }); - const { isPaused } = useClaimBondFormData(); + const { isPaused, maxValues } = useClaimBondFormData(); if (isPaused) { return ; } - // TODO: disable - // TODO: nothing to claim + const isNothingToClaim = !!maxValues?.STETH[1]?.isZero(); + const isPullRewards = !!amount?.isZero() && claimRewards; + const text = isNothingToClaim + ? 'Nothing to claim' + : token === TOKENS.ETH + ? 'Request withdrawal to the Rewards Address' + : isPullRewards + ? 'Claim rewards to the Bond balance' + : 'Claim to the Rewards Address'; + return ( - - {claimRewards ? 'Claim Rewards and Bond' : 'Claim Bond'} - + {text} ); }; From 5d4e240acd9a5baeb3cb823f25d45f2b38bbb091 Mon Sep 17 00:00:00 2001 From: exromany Date: Fri, 28 Mar 2025 00:46:59 +0700 Subject: [PATCH 48/58] feat: improve claim tx modals --- .../claim-bond-form/claim-bond-form-info.tsx | 10 ++- .../context/use-claim-bond-submit.ts | 27 ++++---- .../hooks/use-bond-receive-amount.ts | 20 ------ .../hooks/use-tx-modal-stages-claim-bond.tsx | 64 +++++++----------- shared/hooks/index.ts | 2 + shared/hooks/use-bond-will-receive.ts | 22 +++++++ .../hooks/use-steth-amount.ts | 4 +- .../tx-stages-composed/index.ts | 1 + .../tx-stages-composed/tx-stage-claim.tsx | 66 +++++++++++++++++++ 9 files changed, 136 insertions(+), 80 deletions(-) delete mode 100644 features/claim-bond/claim-bond-form/hooks/use-bond-receive-amount.ts create mode 100644 shared/hooks/use-bond-will-receive.ts rename {features/claim-bond/claim-bond-form => shared}/hooks/use-steth-amount.ts (69%) create mode 100644 shared/transaction-modal/tx-stages-composed/tx-stage-claim.tsx diff --git a/features/claim-bond/claim-bond-form/claim-bond-form-info.tsx b/features/claim-bond/claim-bond-form/claim-bond-form-info.tsx index 3c5994f4..afe106b3 100644 --- a/features/claim-bond/claim-bond-form/claim-bond-form-info.tsx +++ b/features/claim-bond/claim-bond-form/claim-bond-form-info.tsx @@ -5,15 +5,15 @@ import { useWatch } from 'react-hook-form'; import { FormatToken } from 'shared/formatters'; import styled from 'styled-components'; import { ClaimBondFormInputType, useClaimBondFormData } from './context'; -import { useBondReceiveAmount } from './hooks/use-bond-receive-amount'; import { Address } from 'shared/components'; import { AddressContainerStyle, AddressStyle, } from 'shared/components/address/styles'; +import { useBondWillReceive } from 'shared/hooks'; export const ClaimBondFormInfo = () => { - const { rewardsAddress } = useClaimBondFormData(); + const { rewardsAddress, rewards } = useClaimBondFormData(); const [token, amount, claimRewards] = useWatch< ClaimBondFormInputType, ['token', 'amount', 'claimRewards'] @@ -21,7 +21,11 @@ export const ClaimBondFormInfo = () => { name: ['token', 'amount', 'claimRewards'], }); - const bondReceive = useBondReceiveAmount(); + const [bondReceive] = useBondWillReceive( + token, + amount, + claimRewards ? rewards?.available : undefined, + ); return ( diff --git a/features/claim-bond/claim-bond-form/context/use-claim-bond-submit.ts b/features/claim-bond/claim-bond-form/context/use-claim-bond-submit.ts index d234a54b..172cb79b 100644 --- a/features/claim-bond/claim-bond-form/context/use-claim-bond-submit.ts +++ b/features/claim-bond/claim-bond-form/context/use-claim-bond-submit.ts @@ -4,12 +4,7 @@ import invariant from 'tiny-invariant'; import { Zero } from '@ethersproject/constants'; import { TOKENS } from 'consts/tokens'; -import { - useCSAccountingRPC, - useCSAccountingWeb3, - useCSModuleWeb3, - useSendTx, -} from 'shared/hooks'; +import { useCSAccountingWeb3, useCSModuleWeb3, useSendTx } from 'shared/hooks'; import { handleTxError } from 'shared/transaction-modal'; import { NodeOperatorId, RewardProof } from 'types'; import { runWithTransactionLogger } from 'utils'; @@ -88,7 +83,6 @@ export const useClaimBondSubmit = ({ onRetry, }: UseClaimBondOptions) => { const { txModalStages } = useTxModalStagesClaimBond(); - const CSAccounting = useCSAccountingRPC(); const getTx = useClaimBondTx(); const sendTx = useSendTx(); @@ -102,7 +96,12 @@ export const useClaimBondSubmit = ({ invariant(nodeOperatorId, 'NodeOperatorId is not defined'); try { - txModalStages.sign({ amount, token }); + txModalStages.sign({ + amount, + token, + claimRewards, + rewards: rewards?.available, + }); const tx = await getTx(token, { nodeOperatorId, @@ -115,23 +114,23 @@ export const useClaimBondSubmit = ({ () => sendTx(tx), ); - txModalStages.pending({ amount, token }, txHash); + txModalStages.pending( + { amount, token, claimRewards, rewards: rewards?.available }, + txHash, + ); await runWithTransactionLogger('ClaimBond block confirmation', waitTx); await onConfirm?.(); - // TODO: move to onConfirm - const { current } = await CSAccounting.getBondSummary(nodeOperatorId); - - txModalStages.success({ balance: current, amount, token }, txHash); + txModalStages.success({ amount, token }, txHash); return true; } catch (error) { return handleTxError(error, txModalStages, onRetry); } }, - [getTx, txModalStages, onConfirm, CSAccounting, sendTx, onRetry], + [getTx, txModalStages, onConfirm, sendTx, onRetry], ); return { diff --git a/features/claim-bond/claim-bond-form/hooks/use-bond-receive-amount.ts b/features/claim-bond/claim-bond-form/hooks/use-bond-receive-amount.ts deleted file mode 100644 index abeccfd1..00000000 --- a/features/claim-bond/claim-bond-form/hooks/use-bond-receive-amount.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Zero } from '@ethersproject/constants'; -import { useWatch } from 'react-hook-form'; -import { ClaimBondFormInputType, useClaimBondFormData } from '../context'; -import { useStethAmount } from './use-steth-amount'; - -export const useBondReceiveAmount = () => { - const [token, amount] = useWatch< - ClaimBondFormInputType, - ['token', 'amount', 'claimRewards'] - >({ - name: ['token', 'amount', 'claimRewards'], - }); - - const { rewards } = useClaimBondFormData(); - - const stethAmount = useStethAmount(token, amount ?? Zero); - const bondReceive = rewards?.available.sub(stethAmount ?? Zero) ?? Zero; - - return bondReceive.lt(0) ? Zero : bondReceive; -}; diff --git a/features/claim-bond/claim-bond-form/hooks/use-tx-modal-stages-claim-bond.tsx b/features/claim-bond/claim-bond-form/hooks/use-tx-modal-stages-claim-bond.tsx index 97c2c312..2c2f52e1 100644 --- a/features/claim-bond/claim-bond-form/hooks/use-tx-modal-stages-claim-bond.tsx +++ b/features/claim-bond/claim-bond-form/hooks/use-tx-modal-stages-claim-bond.tsx @@ -1,28 +1,28 @@ import type { BigNumber } from 'ethers'; +import { getExternalLinks } from 'consts/external-links'; +import { MATOMO_CLICK_EVENTS_TYPES } from 'consts/matomo-click-events'; import { TOKENS } from 'consts/tokens'; +import { PATH } from 'consts/urls'; import { MatomoLink } from 'shared/components'; import { TxLinkEtherscan } from 'shared/components/tx-link-etherscan'; +import { LocalLink } from 'shared/navigate'; import { TransactionModalTransitStage, TxAmount, - TxStageOperationSucceedBalanceShown, - TxStageSignOperationAmount, + TxStageClaim, TxStageSuccess, getGeneralTransactionModalStages, useTransactionModalStage, } from 'shared/transaction-modal'; -import { getExternalLinks } from 'consts/external-links'; -import { LocalLink } from 'shared/navigate'; -import { PATH } from 'consts/urls'; -import { MATOMO_CLICK_EVENTS_TYPES } from 'consts/matomo-click-events'; -const STAGE_OPERATION_ARGS = { - operationText: 'Claiming Bond', +type Props = { + amount: BigNumber; + token: TOKENS; + claimRewards: boolean; + rewards?: BigNumber; }; - -type Props = { amount: BigNumber; token: TOKENS }; -type SuccessProps = { amount: BigNumber; balance: BigNumber; token: TOKENS }; +type SuccessProps = { amount: BigNumber; token: TOKENS }; const { stakeWidget } = getExternalLinks(); @@ -31,33 +31,12 @@ const getTxModalStagesClaimBond = ( ) => ({ ...getGeneralTransactionModalStages(transitStage), - sign: ({ amount, token }: Props) => - transitStage( - , - ), + sign: (props: Props) => transitStage(), - pending: ({ amount, token }: Props, txHash?: string) => - transitStage( - , - ), + pending: (props: Props, txHash?: string) => + transitStage(), - success: ({ amount, balance, token }: SuccessProps, txHash?: string) => + success: ({ amount, token }: SuccessProps, txHash?: string) => transitStage( token === TOKENS.ETH ? ( // TODO: matomo events @@ -88,11 +67,14 @@ const getTxModalStagesClaimBond = ( } /> ) : ( - + Transaction can be viewed on{' '} + . + + } /> ), { diff --git a/shared/hooks/index.ts b/shared/hooks/index.ts index 01cdd3c7..bfb7d2d3 100644 --- a/shared/hooks/index.ts +++ b/shared/hooks/index.ts @@ -4,6 +4,7 @@ export * from './use-approve'; export * from './use-ask-how-did-you-learn-csm'; export * from './use-await-network-data'; export * from './use-awaiter'; +export * from './use-bond-will-receive'; export * from './use-can-create-node-operator'; export * from './use-compare-with-router-path'; export * from './use-confirm-modal'; @@ -35,6 +36,7 @@ export * from './use-router-path'; export * from './use-send-tx'; export * from './use-show-rule'; export * from './use-sorted-keys'; +export * from './use-steth-amount'; export * from './use-surveys-call'; export * from './use-token-max-amount'; export * from './use-tx-cost-in-usd'; diff --git a/shared/hooks/use-bond-will-receive.ts b/shared/hooks/use-bond-will-receive.ts new file mode 100644 index 00000000..665f2883 --- /dev/null +++ b/shared/hooks/use-bond-will-receive.ts @@ -0,0 +1,22 @@ +import { TOKENS } from 'consts/tokens'; +import { BigNumber } from 'ethers'; +import { useStethAmount } from './use-steth-amount'; +import { Zero } from '@ethersproject/constants'; + +export const useBondWillReceive = ( + token: TOKENS, + amount?: BigNumber, + rewards?: BigNumber, +) => { + const stethAmount = useStethAmount(token, amount); + + return [ + (amount && + stethAmount && + rewards && + rewards.gt(stethAmount) && + rewards.sub(stethAmount)) || + Zero, + !!(amount && stethAmount && rewards && stethAmount.gt(rewards)), + ] as const; +}; diff --git a/features/claim-bond/claim-bond-form/hooks/use-steth-amount.ts b/shared/hooks/use-steth-amount.ts similarity index 69% rename from features/claim-bond/claim-bond-form/hooks/use-steth-amount.ts rename to shared/hooks/use-steth-amount.ts index 275118a2..02cdd08f 100644 --- a/features/claim-bond/claim-bond-form/hooks/use-steth-amount.ts +++ b/shared/hooks/use-steth-amount.ts @@ -1,8 +1,8 @@ +import { Zero } from '@ethersproject/constants'; import { TOKENS } from 'consts/tokens'; -import { BigNumber } from 'ethers'; import { useStethByWsteth } from 'shared/hooks'; -export const useStethAmount = (token: TOKENS, amount?: BigNumber) => { +export const useStethAmount = (token: TOKENS, amount = Zero) => { const { data: wstethToSteth } = useStethByWsteth( (token === TOKENS.WSTETH && amount) || undefined, ); diff --git a/shared/transaction-modal/tx-stages-composed/index.ts b/shared/transaction-modal/tx-stages-composed/index.ts index da3c79bc..c712c667 100644 --- a/shared/transaction-modal/tx-stages-composed/index.ts +++ b/shared/transaction-modal/tx-stages-composed/index.ts @@ -1,3 +1,4 @@ export * from './tx-stage-amount-operation'; export * from './tx-stage-keys-operation'; export * from './tx-stage-operation-succeed-balance-shown'; +export * from './tx-stage-claim'; diff --git a/shared/transaction-modal/tx-stages-composed/tx-stage-claim.tsx b/shared/transaction-modal/tx-stages-composed/tx-stage-claim.tsx new file mode 100644 index 00000000..d7d48198 --- /dev/null +++ b/shared/transaction-modal/tx-stages-composed/tx-stage-claim.tsx @@ -0,0 +1,66 @@ +import { TxStagePending } from '../tx-stages-basic/tx-stage-pending'; +import { TxStageSign } from '../tx-stages-basic/tx-stage-sign'; +import { TxAmount } from '../tx-stages-parts/tx-amount'; + +import { TOKENS } from 'consts/tokens'; +import type { BigNumber } from 'ethers'; +import { useBondWillReceive } from 'shared/hooks'; + +type TxStageClaimProps = { + claimRewards: boolean; + amount: BigNumber; + token: TOKENS; + rewards?: BigNumber; + isPending?: boolean; + txHash?: string; +}; + +export const TxStageClaim = ({ + claimRewards, + amount, + token, + rewards, + isPending, + txHash, +}: TxStageClaimProps) => { + const [bondReceive, amountBiggerRewards] = useBondWillReceive( + token, + amount, + claimRewards ? rewards : undefined, + ); + + const operationText = + token === 'ETH' ? 'requesting withdrawal of' : 'claiming'; + const sourceText = claimRewards + ? amountBiggerRewards + ? 'bond and rewards' + : 'rewards' + : 'bond'; + const Component = isPending ? TxStagePending : TxStageSign; + + return ( + + You are {operationText} {sourceText} + + } + description={ + <> +

    + Rewards Address will receive{' '} + . +

    + + {bondReceive.gt(0) && ( +

    + Bond balance will increase by{' '} + . +

    + )} + + } + /> + ); +}; From f61d9fb6a95d5594a704b0cc093c46e8ad3e425c Mon Sep 17 00:00:00 2001 From: exromany Date: Fri, 28 Mar 2025 00:54:22 +0700 Subject: [PATCH 49/58] chore: hide note if keys < 3 --- features/remove-keys/remove-keys/controls/keys-selector.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/features/remove-keys/remove-keys/controls/keys-selector.tsx b/features/remove-keys/remove-keys/controls/keys-selector.tsx index 4c8447b7..5db4d415 100644 --- a/features/remove-keys/remove-keys/controls/keys-selector.tsx +++ b/features/remove-keys/remove-keys/controls/keys-selector.tsx @@ -9,7 +9,9 @@ export const KeysSelector = () => { <> Choose keys to remove - Your choice has to be a sequential array + {keys && keys?.length > 2 && ( + Your choice has to be a sequential array + )} ); }; From fbccd6499f528b8b4973570ecfe00e603bf408ce Mon Sep 17 00:00:00 2001 From: exromany Date: Mon, 31 Mar 2025 14:07:44 +0300 Subject: [PATCH 50/58] fix: show survey auth error text --- features/surveys/shared/survey-auth-provider.tsx | 1 + features/surveys/shared/use-modal-stages.tsx | 6 +++++- features/surveys/shared/use-siwe.ts | 4 ++-- features/surveys/survey-contacts/use-modal-stages.tsx | 6 +++++- features/surveys/survey-experience/use-modal-stages.tsx | 6 +++++- .../survey-how-did-you-learn-csm/use-modal-stages.tsx | 6 +++++- features/surveys/survey-setup/use-modal-stages.tsx | 6 +++++- shared/transaction-modal/tx-stages-basic/tx-stage-fail.tsx | 6 ++++-- 8 files changed, 32 insertions(+), 9 deletions(-) diff --git a/features/surveys/shared/survey-auth-provider.tsx b/features/surveys/shared/survey-auth-provider.tsx index 91594045..611c849c 100644 --- a/features/surveys/shared/survey-auth-provider.tsx +++ b/features/surveys/shared/survey-auth-provider.tsx @@ -57,6 +57,7 @@ export const SurveyAuthProvider: FC = ({ children }) => { }); if (!response.ok) { modalStages.failed(await extractError(response)); + return; } const data: { access_token: string; token_type: string } = await response.json(); diff --git a/features/surveys/shared/use-modal-stages.tsx b/features/surveys/shared/use-modal-stages.tsx index 1007966a..b5fb232d 100644 --- a/features/surveys/shared/use-modal-stages.tsx +++ b/features/surveys/shared/use-modal-stages.tsx @@ -20,7 +20,11 @@ const getModalStages = (transitStage: TransactionModalTransitStage) => ({ failed: (error: unknown) => transitStage( - , + , ), rejected: () => diff --git a/features/surveys/shared/use-siwe.ts b/features/surveys/shared/use-siwe.ts index 8409f15f..be29e620 100644 --- a/features/surveys/shared/use-siwe.ts +++ b/features/surveys/shared/use-siwe.ts @@ -1,5 +1,5 @@ import { useSDK } from '@lido-sdk/react'; -import { addHours } from 'date-fns'; +import { addDays } from 'date-fns'; import { useCallback } from 'react'; import { SiweMessage } from 'siwe'; import invariant from 'tiny-invariant'; @@ -18,7 +18,7 @@ const createSiweMessage = (address: string, chainId?: number) => { uri, version: '1', chainId, - expirationTime: addHours(new Date(), 1).toISOString(), + expirationTime: addDays(new Date(), 1).toISOString(), }); return message.prepareMessage(); }; diff --git a/features/surveys/survey-contacts/use-modal-stages.tsx b/features/surveys/survey-contacts/use-modal-stages.tsx index eadfd61b..8aca5148 100644 --- a/features/surveys/survey-contacts/use-modal-stages.tsx +++ b/features/surveys/survey-contacts/use-modal-stages.tsx @@ -38,7 +38,11 @@ const getModalStages = (transitStage: TransactionModalTransitStage) => ({ failed: (error: unknown) => transitStage( - , + , ), }); diff --git a/features/surveys/survey-experience/use-modal-stages.tsx b/features/surveys/survey-experience/use-modal-stages.tsx index 31f13dd8..c07d489e 100644 --- a/features/surveys/survey-experience/use-modal-stages.tsx +++ b/features/surveys/survey-experience/use-modal-stages.tsx @@ -26,7 +26,11 @@ const getModalStages = (transitStage: TransactionModalTransitStage) => ({ failed: (error: unknown) => transitStage( - , + , ), }); diff --git a/features/surveys/survey-how-did-you-learn-csm/use-modal-stages.tsx b/features/surveys/survey-how-did-you-learn-csm/use-modal-stages.tsx index 4a29a0d7..c3617ffe 100644 --- a/features/surveys/survey-how-did-you-learn-csm/use-modal-stages.tsx +++ b/features/surveys/survey-how-did-you-learn-csm/use-modal-stages.tsx @@ -21,7 +21,11 @@ const getModalStages = (transitStage: TransactionModalTransitStage) => ({ failed: (error: unknown) => transitStage( - , + , ), }); diff --git a/features/surveys/survey-setup/use-modal-stages.tsx b/features/surveys/survey-setup/use-modal-stages.tsx index 7ba4368a..4035aa67 100644 --- a/features/surveys/survey-setup/use-modal-stages.tsx +++ b/features/surveys/survey-setup/use-modal-stages.tsx @@ -31,7 +31,11 @@ const getModalStages = (transitStage: TransactionModalTransitStage) => ({ failed: (error: unknown) => transitStage( - , + , ), }); diff --git a/shared/transaction-modal/tx-stages-basic/tx-stage-fail.tsx b/shared/transaction-modal/tx-stages-basic/tx-stage-fail.tsx index f4e389ed..4fba9dab 100644 --- a/shared/transaction-modal/tx-stages-basic/tx-stage-fail.tsx +++ b/shared/transaction-modal/tx-stages-basic/tx-stage-fail.tsx @@ -1,4 +1,4 @@ -import { FC, useCallback, useState } from 'react'; +import { FC, ReactNode, useCallback, useState } from 'react'; import { Loader } from '@lidofinance/lido-ui'; import { TransactionModalContent } from 'shared/transaction-modal/transaction-modal-content'; @@ -11,11 +11,13 @@ type TxStageFailProps = { code?: ErrorCode; title?: string; onRetry?: React.MouseEventHandler; + error?: ReactNode; }; export const TxStageFail: FC = ({ code = ErrorCode.SOMETHING_WRONG, title = 'Transaction Failed', + error, onRetry, }) => { const [isLoading, setLoading] = useState(false); @@ -30,7 +32,7 @@ export const TxStageFail: FC = ({ } - description={ErrorMessages[code]} + description={error ?? ErrorMessages[code]} footerHint={ code !== ErrorCode.NOT_ENOUGH_ETHER && onRetry && From 8ad9d734ec01d541604e0b900559ad30f6102e10 Mon Sep 17 00:00:00 2001 From: exromany Date: Wed, 2 Apr 2025 20:51:10 +0300 Subject: [PATCH 51/58] fix: use cached nodeOperatorId if fetching failed --- providers/node-operator-provider/use-node-operators-list.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/providers/node-operator-provider/use-node-operators-list.ts b/providers/node-operator-provider/use-node-operators-list.ts index e0aa5327..f4bdb645 100644 --- a/providers/node-operator-provider/use-node-operators-list.ts +++ b/providers/node-operator-provider/use-node-operators-list.ts @@ -19,7 +19,10 @@ export const useNodeOperatorsList = () => { [mutate], ); - const list = useMemo(() => data ?? (cached ? [cached] : []), [cached, data]); + const list = useMemo( + () => (data?.length ? data : cached ? [cached] : []), + [cached, data], + ); return { list, From 86565af6d357cbece9e2b78128d4d212d8c57022 Mon Sep 17 00:00:00 2001 From: exromany Date: Thu, 3 Apr 2025 08:29:48 +0300 Subject: [PATCH 52/58] fix: append node operator --- consts/csm-constants.ts | 2 +- pages/no/[id].tsx | 15 ++++++++------- .../use-node-operators-list.ts | 9 ++++++++- .../use-node-operators-fetcher-from-events.ts | 5 ++++- types/node-operator.ts | 4 ++-- utils/get-first-param.ts | 2 ++ utils/index.ts | 1 + utils/merge-roles.ts | 10 ++++++---- 8 files changed, 32 insertions(+), 16 deletions(-) create mode 100644 utils/get-first-param.ts diff --git a/consts/csm-constants.ts b/consts/csm-constants.ts index 54b7145b..a84c9fd5 100644 --- a/consts/csm-constants.ts +++ b/consts/csm-constants.ts @@ -57,7 +57,7 @@ export const CONSTANTS_BY_NETWORK: Record = { ExitBusOracle: '0x8664d394C2B3278F26A1B44B967aEf99707eeAB2', StakingRouter: '0xCc820558B39ee15C7C45B59390B503b83fb499A8', }, - deploymentBlockNumber: undefined, + deploymentBlockNumber: '0x1374', stakingModuleId: 4, withdrawalCredentials: '0x4473dCDDbf77679A643BdB654dbd86D67F8d32f2', retentionPeriodMins: 80_640, // 8 weeks diff --git a/pages/no/[id].tsx b/pages/no/[id].tsx index 6ad069ff..7665366a 100644 --- a/pages/no/[id].tsx +++ b/pages/no/[id].tsx @@ -2,20 +2,21 @@ import { PATH } from 'consts/urls'; import { useRouter } from 'next/router'; import { useNodeOperatorContext } from 'providers/node-operator-provider'; import { useEffect } from 'react'; -import { useSearchParams } from 'shared/hooks'; +import { useAccount } from 'shared/hooks'; import { SplashPage } from 'shared/navigate'; import { NodeOperatorId } from 'types'; +import { getFirstParam } from 'utils'; const Page = () => { - const { push } = useRouter(); - const query = useSearchParams(); + const { push, query, ...all } = useRouter(); const { appendAndSwitch } = useNodeOperatorContext(); + const { address } = useAccount(); useEffect(() => { - if (!query) return; + if (!query || !address) return; const apply = async () => { - const queryId = query.get('id') ?? ''; + const queryId = getFirstParam(query['id']) ?? ''; const numberId = parseInt(queryId); if (!Number.isNaN(numberId)) { const id: NodeOperatorId = `${numberId}`; @@ -24,8 +25,8 @@ const Page = () => { void push(PATH.HOME); }; - void apply(); - }, [appendAndSwitch, push, query]); + setTimeout(() => void apply(), 100); + }, [address, all, appendAndSwitch, push, query]); return ; }; diff --git a/providers/node-operator-provider/use-node-operators-list.ts b/providers/node-operator-provider/use-node-operators-list.ts index f4bdb645..610c9369 100644 --- a/providers/node-operator-provider/use-node-operators-list.ts +++ b/providers/node-operator-provider/use-node-operators-list.ts @@ -3,6 +3,7 @@ import { useCsmNodeOperators } from 'shared/hooks'; import { NodeOperator } from 'types'; import { mergeRoles } from 'utils'; import { useCachedNodeOperator } from './use-cached-node-operator'; +import { ROLES } from 'consts/roles'; export const useNodeOperatorsList = () => { const { data, initialLoading, mutate } = useCsmNodeOperators(); @@ -13,7 +14,13 @@ export const useNodeOperatorsList = () => { (income: NodeOperator) => { // TODO: fix for spectacular if (income.roles.length > 0) { - void mutate((prev = []) => [...mergeRoles(prev, income)]); + void mutate((prev = []) => [ + ...mergeRoles(prev, { + id: income.id, + manager: income.roles.includes(ROLES.MANAGER), + rewards: income.roles.includes(ROLES.REWARDS), + }), + ]); } }, [mutate], diff --git a/shared/hooks/use-node-operators-fetcher-from-events.ts b/shared/hooks/use-node-operators-fetcher-from-events.ts index ada485a4..461cb8b7 100644 --- a/shared/hooks/use-node-operators-fetcher-from-events.ts +++ b/shared/hooks/use-node-operators-fetcher-from-events.ts @@ -21,7 +21,8 @@ type NodeOperatorRoleEvent = | NodeOperatorManagerAddressChangedEvent; const restoreEvents = (events: NodeOperatorRoleEvent[], address?: Address) => { - const isUserAddress = (value: string) => compareLowercase(address, value); + const isUserAddress = (value: string) => + compareLowercase(address, value) || null; return events .sort((a, b) => a.blockNumber - b.blockNumber) @@ -38,10 +39,12 @@ const restoreEvents = (events: NodeOperatorRoleEvent[], address?: Address) => { return mergeRoles(prev, { id, manager: isUserAddress(e.args[2]), + rewards: false, }); case 'NodeOperatorRewardAddressChanged': return mergeRoles(prev, { id, + manager: false, rewards: isUserAddress(e.args[2]), }); default: diff --git a/types/node-operator.ts b/types/node-operator.ts index 447c6d2e..89a9253e 100644 --- a/types/node-operator.ts +++ b/types/node-operator.ts @@ -11,8 +11,8 @@ export type NodeOperator = { export type NodeOperatorRoles = { id: NodeOperatorId; - manager?: boolean; - rewards?: boolean; + manager: boolean | null; + rewards: boolean | null; }; export type NodeOperatorInvite = { diff --git a/utils/get-first-param.ts b/utils/get-first-param.ts new file mode 100644 index 00000000..efb0164f --- /dev/null +++ b/utils/get-first-param.ts @@ -0,0 +1,2 @@ +export const getFirstParam = (param: string | string[] | undefined) => + Array.isArray(param) ? param[0] : param; diff --git a/utils/index.ts b/utils/index.ts index 854d47b9..f3c73289 100644 --- a/utils/index.ts +++ b/utils/index.ts @@ -17,6 +17,7 @@ export * from './formatHex'; export * from './formatKeys'; export * from './get-added-node-operator'; export * from './get-based-hash-href'; +export * from './get-first-param'; export * from './get-ipfs-base-path'; export * from './get-max-balance-token'; export * from './get-node-operator-id-from-event'; diff --git a/utils/merge-roles.ts b/utils/merge-roles.ts index e9e4d61b..c6cb41dc 100644 --- a/utils/merge-roles.ts +++ b/utils/merge-roles.ts @@ -8,11 +8,13 @@ const applyPatch = ( id: patch.id, roles: packRoles({ manager: - patch.manager !== false && - (patch.manager || item?.roles.includes(ROLES.MANAGER)), + (patch.manager !== null && + (patch.manager || item?.roles.includes(ROLES.MANAGER))) || + false, rewards: - patch.rewards !== false && - (patch.rewards || item?.roles.includes(ROLES.REWARDS)), + (patch.rewards !== null && + (patch.rewards || item?.roles.includes(ROLES.REWARDS))) || + false, }), }); From 28b599b759589040ddaf3ed4624940940d9cad8b Mon Sep 17 00:00:00 2001 From: exromany Date: Thu, 3 Apr 2025 09:31:21 +0300 Subject: [PATCH 53/58] fix: limit rpc batch size --- .gitignore | 1 + providers/sdk-legacy.tsx | 2 +- providers/web3.tsx | 2 +- utils/getStaticRpcBatchProvider.ts | 166 +++++++++++++++++++++++++++++ 4 files changed, 169 insertions(+), 2 deletions(-) create mode 100644 utils/getStaticRpcBatchProvider.ts diff --git a/.gitignore b/.gitignore index 0a71a893..c3d52525 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,4 @@ yarn-error.log* .idea /public/runtime/ +.qodo diff --git a/providers/sdk-legacy.tsx b/providers/sdk-legacy.tsx index 011cea22..cd448e9c 100644 --- a/providers/sdk-legacy.tsx +++ b/providers/sdk-legacy.tsx @@ -1,10 +1,10 @@ import React, { useEffect, useState } from 'react'; import { ProviderSDK } from '@lido-sdk/react'; -import { getStaticRpcBatchProvider } from '@lido-sdk/providers'; import { Web3Provider } from '@ethersproject/providers'; import { Chain, useAccount as useWagmiAccount } from 'wagmi'; import { mainnet } from 'wagmi/chains'; import { useAccount } from 'shared/hooks'; +import { getStaticRpcBatchProvider } from 'utils/getStaticRpcBatchProvider'; const POLLING_INTERVAL = 12_000; diff --git a/providers/web3.tsx b/providers/web3.tsx index 95079ebf..78ef8f8e 100644 --- a/providers/web3.tsx +++ b/providers/web3.tsx @@ -2,7 +2,6 @@ import { FC, PropsWithChildren, useMemo } from 'react'; import { ReefKnot, getConnectors, holesky } from 'reef-knot/core-react'; import { WagmiConfig, createClient, configureChains, Chain } from 'wagmi'; import * as wagmiChains from 'wagmi/chains'; -import { getStaticRpcBatchProvider } from '@lido-sdk/providers'; import { hoodi } from 'consts/hoodi'; import { useUserConfig } from 'config/user-config'; @@ -11,6 +10,7 @@ import { CHAINS } from 'consts/chains'; import { ConnectWalletModal } from 'shared/wallet/connect-wallet-modal'; import { SDKLegacyProvider } from './sdk-legacy'; +import { getStaticRpcBatchProvider } from 'utils/getStaticRpcBatchProvider'; const wagmiChainsArray = Object.values({ ...wagmiChains, hoodi, holesky }); diff --git a/utils/getStaticRpcBatchProvider.ts b/utils/getStaticRpcBatchProvider.ts new file mode 100644 index 00000000..73618a11 --- /dev/null +++ b/utils/getStaticRpcBatchProvider.ts @@ -0,0 +1,166 @@ +import { JsonRpcProvider, Network } from '@ethersproject/providers'; +import { CHAINS } from '@lido-sdk/constants'; +import { + deepCopy, + defineReadOnly, + fetchJson, + Logger, +} from 'ethers/lib/utils.js'; + +const MAX_BATCH_SIZE = 20; + +const createProviderGetter =

    ( + Provider: P, +) => { + const cache = new Map>(); + + return ( + chainId: CHAINS, + url: string, + cacheSeed = 0, + pollingInterval: number | null = null, + ): InstanceType

    => { + const cacheKey = `${chainId}-${cacheSeed}-${url}`; + let provider = cache.get(cacheKey); + + if (!provider) { + provider = new Provider(url, chainId) as InstanceType

    ; + cache.set(cacheKey, provider); + } + + if (pollingInterval) { + provider.pollingInterval = pollingInterval; + } + + return provider; + }; +}; + +const logger = new Logger('StaticJsonRpcBatchProvider/1.0'); + +class StaticJsonRpcBatchProvider extends JsonRpcProvider { + async detectNetwork(): Promise { + let network = this.network; + + if (network == null) { + network = await super.detectNetwork(); + + if (!network) { + logger.throwError( + 'no network detected', + Logger.errors.UNKNOWN_ERROR, + {}, + ); + } + + // If still not set, set it + if (this._network == null) { + // A static network does not support "any" + defineReadOnly(this, '_network', network); + + this.emit('network', network, null); + } + } + + return network; + } + + _pendingBatchAggregator: NodeJS.Timer | null = null; + _pendingBatch: Array<{ + request: { method: string; params: Array; id: number; jsonrpc: '2.0' }; + resolve: (result: any) => void; + reject: (error: Error) => void; + }> = []; + + send(method: string, params: Array): Promise { + const request = { + method: method, + params: params, + id: this._nextId++, + jsonrpc: '2.0', + }; + + const inflightRequest: any = { request, resolve: null, reject: null }; + + const promise = new Promise((resolve, reject) => { + inflightRequest.resolve = resolve; + inflightRequest.reject = reject; + }); + + this._pendingBatch.push(inflightRequest); + + // eslint-disable-next-line unicorn/consistent-function-scoping + const runAggregator = () => { + if (this._pendingBatchAggregator) { + return; + } + // Schedule batch for next event loop + short duration + this._pendingBatchAggregator = setTimeout(() => { + // Get teh current batch and clear it, so new requests + // go into the next batch + const batch = this._pendingBatch.splice(0, MAX_BATCH_SIZE); + this._pendingBatchAggregator = null; + if (batch.length === 0) { + return; + } + if (this._pendingBatch.length > 0) { + runAggregator(); + } + + // Get the request as an array of requests + const request = batch.map((inflight) => inflight.request); + + this.emit('debug', { + action: 'requestBatch', + request: deepCopy(request), + provider: this, + }); + + return fetchJson(this.connection, JSON.stringify(request)).then( + (result) => { + this.emit('debug', { + action: 'response', + request: request, + response: result, + provider: this, + }); + + // For each result, feed it to the correct Promise, depending + // on whether it was a success or error + batch.forEach((inflightRequest, index) => { + const payload = result[index]; + if (payload.error) { + const error = new Error(payload.error.message); + (error).code = payload.error.code; + (error).data = payload.error.data; + inflightRequest.reject(error); + } else { + inflightRequest.resolve(payload.result); + } + }); + }, + (error) => { + this.emit('debug', { + action: 'response', + error: error, + request: request, + provider: this, + }); + + batch.forEach((inflightRequest) => { + inflightRequest.reject(error); + }); + }, + ); + }, 10); + }; + + runAggregator(); + + return promise; + } +} + +export const getStaticRpcBatchProvider = createProviderGetter( + StaticJsonRpcBatchProvider, +); From b95ff4d6c0060f77560a4a7eadf98bce683cc2ca Mon Sep 17 00:00:00 2001 From: exromany Date: Thu, 3 Apr 2025 12:23:19 +0300 Subject: [PATCH 54/58] fix: optional page props --- pages/_app.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/_app.tsx b/pages/_app.tsx index 1b12e12b..ba2a6df1 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -35,7 +35,7 @@ const AppWrapper = (props: AppProps): JSX.Element => { return ( {/* see https://nextjs.org/docs/messages/no-document-viewport-meta */} From da7c49cc1576228d26281f092af83782ecabc0a9 Mon Sep 17 00:00:00 2001 From: mateumiralles Date: Mon, 7 Apr 2025 17:20:36 +0200 Subject: [PATCH 55/58] fix: dappnodes hoodi config --- config/rpc/index.ts | 8 ++++++++ dappnode/hooks/use-dappnode-urls.ts | 23 ++++++++++++++++++++++- dappnode/hooks/use-ec-sanity-check.ts | 1 + global.d.ts | 1 + 4 files changed, 32 insertions(+), 1 deletion(-) diff --git a/config/rpc/index.ts b/config/rpc/index.ts index e6d7472e..6c9b7d8b 100644 --- a/config/rpc/index.ts +++ b/config/rpc/index.ts @@ -24,6 +24,10 @@ export const getBackendRPCPath = (chainId: string | number): string => { return ( config.rpcUrls_1 || 'http://execution.mainnet.dncore.dappnode:8545' ); + } else if (parseInt(chainId) === CHAINS.Hoodi) { + return ( + config.rpcUrls_560048 || 'http://execution.hoodi.dncore.dappnode:8545' + ); } else { return ( config.rpcUrls_17000 || 'http://execution.holesky.dncore.dappnode:8545' @@ -34,6 +38,10 @@ export const getBackendRPCPath = (chainId: string | number): string => { return ( config.rpcUrls_1 || 'http://execution.mainnet.dncore.dappnode:8545' ); + } else if (chainId === CHAINS.Hoodi) { + return ( + config.rpcUrls_560048 || 'http://execution.hoodi.dncore.dappnode:8545' + ); } else { return ( config.rpcUrls_17000 || 'http://execution.holesky.dncore.dappnode:8545' diff --git a/dappnode/hooks/use-dappnode-urls.ts b/dappnode/hooks/use-dappnode-urls.ts index 5059313c..4ce8b581 100644 --- a/dappnode/hooks/use-dappnode-urls.ts +++ b/dappnode/hooks/use-dappnode-urls.ts @@ -40,7 +40,7 @@ const useDappnodeUrls = () => { CCStatusApiUrl: '/api/consensus-status-mainnet', keysStatusUrl: '/api/keys-status-mainnet', installerTabUrl: - 'http://my.dappnode/installer/dnp/lido-csm-holesky.dnp.dappnode.eth', + 'http://my.dappnode/installer/dnp/lido-csm-hoodi.dnp.dappnode.eth', MEVApiUrl: '/api/mev-status-mainnet', MEVPackageConfig: 'http://my.dappnode/packages/my/mev-boost.dnp.dappnode.eth/config', @@ -66,6 +66,27 @@ const useDappnodeUrls = () => { MEVPackageConfig: 'http://my.dappnode/packages/my/mev-boost-holesky.dnp.dappnode.eth/config', }, + [CHAINS.Hoodi]: { + brainUrl: 'http://brain.web3signer-hoodi.dappnode', + brainKeysUrl: '/api/brain-keys-hoodi', + brainLaunchpadUrl: '/api/brain-launchpad-hoodi', + signerUrl: 'http://web3signer.web3signer-hoodi.dappnode', + sentinelUrl: 'https://t.me/CSMSentinelHolesky_bot', // DEPRECATED? + stakersUiUrl: 'http://my.dappnode/stakers/hoodi', + backendUrl: 'http://lido-events.lido-csm-hoodi.dappnode:8080', + ECApiUrl: + publicRuntimeConfig.rpcUrls_560048 || + 'http://execution.hoodi.dncore.dappnode:8545', + CCApiUrl: 'http://beacon-chain.hoodi.dncore.dappnode:3500', + CCVersionApiUrl: '/api/consensus-version-hoodi', + CCStatusApiUrl: '/api/consensus-status-hoodi', + keysStatusUrl: '/api/keys-status-hoodi', + installerTabUrl: + 'http://my.dappnode/installer/dnp/lido-csm-mainnet.dnp.dappnode.eth', + MEVApiUrl: '/api/mev-status-hoodi', + MEVPackageConfig: + 'http://my.dappnode/packages/my/mev-boost-hoodi.dnp.dappnode.eth/config', + }, }; const brainUrl = diff --git a/dappnode/hooks/use-ec-sanity-check.ts b/dappnode/hooks/use-ec-sanity-check.ts index 5be07524..af7008de 100644 --- a/dappnode/hooks/use-ec-sanity-check.ts +++ b/dappnode/hooks/use-ec-sanity-check.ts @@ -15,6 +15,7 @@ export const useECSanityCheck = () => { () => ({ [CHAINS.Mainnet]: `0xf5330dbcf09885ed145c4435e356b5d8a10054751bb8009d3a2605d476ac173f`, [CHAINS.Holesky]: `0x1475719ecbb73b28bc531bb54b37695df1bf6b71c6d2bf1d28b4efa404867e26`, + [CHAINS.Hoodi]: `0xebc45a0fa30a3f9badbcc4448ea22cef1a5d18b97825802a70df31cecb59127d`, }), [], ); diff --git a/global.d.ts b/global.d.ts index 2071eed0..cde058ba 100644 --- a/global.d.ts +++ b/global.d.ts @@ -48,6 +48,7 @@ declare module 'next/config' { // DAPPNODE rpcUrls_1: string | undefined; rpcUrls_17000: string | undefined; + rpcUrls_560048: string | undefined; defaultChain: number | undefined; supportedChains: number[] | undefined; }; From a6a59ae0eaaacdf9af8660f6610f6b47e43733bd Mon Sep 17 00:00:00 2001 From: mateumiralles Date: Tue, 8 Apr 2025 11:02:17 +0200 Subject: [PATCH 56/58] fix: hoodi rewrites --- next.config.mjs | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/next.config.mjs b/next.config.mjs index 05dfbe74..32816516 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -139,6 +139,16 @@ export default withBundleAnalyzer({ destination: 'http://beacon-chain.holesky.dncore.dappnode:3500/eth/v1/node/syncing', }, + { + source: '/api/consensus-version-hoodi', + destination: + 'http://beacon-chain.hoodi.dncore.dappnode:3500/eth/v1/node/version', + }, + { + source: '/api/consensus-status-hoodi', + destination: + 'http://beacon-chain.hoodi.dncore.dappnode:3500/eth/v1/node/syncing', + }, { source: '/api/keys-status-mainnet', destination: @@ -149,6 +159,11 @@ export default withBundleAnalyzer({ destination: 'http://beacon-chain.holesky.dncore.dappnode:3500/eth/v1/beacon/states/head/validators', }, + { + source: '/api/keys-status-hoodi', + destination: + 'http://beacon-chain.hoodi.dncore.dappnode:3500/eth/v1/beacon/states/head/validators', + }, { source: '/api/brain-keys-mainnet', destination: @@ -159,6 +174,11 @@ export default withBundleAnalyzer({ destination: 'http://brain.web3signer-holesky.dappnode:5000/api/v0/brain/validators?tag=lido&format=pubkey', }, + { + source: '/api/brain-keys-hoodi', + destination: + 'http://brain.web3signer-hoodi.dappnode:5000/api/v0/brain/validators?tag=lido&format=pubkey', + }, { source: '/api/brain-launchpad-mainnet', destination: 'http://brain.web3signer.dappnode:3000/eth/v1/keystores', @@ -168,6 +188,11 @@ export default withBundleAnalyzer({ destination: 'http://brain.web3signer-holesky.dappnode:3000/eth/v1/keystores', }, + { + source: '/api/brain-launchpad-hoodi', + destination: + 'http://brain.web3signer-hoodi.dappnode:3000/eth/v1/keystores', + }, { source: '/api/mev-status-mainnet', destination: 'http://mev-boost.dappnode:18550/', @@ -176,6 +201,10 @@ export default withBundleAnalyzer({ source: '/api/mev-status-holesky', destination: 'http://mev-boost-holesky.dappnode:18550/', }, + { + source: '/api/mev-status-hoodi', + destination: 'http://mev-boost-hoodi.dappnode:18550/', + }, ]; }, @@ -250,6 +279,7 @@ export default withBundleAnalyzer({ // DAPPNODE rpcUrls_1: process.env.EL_RPC_URLS_1, rpcUrls_17000: process.env.EL_RPC_URLS_17000, + rpcUrls_560048: process.env.EL_RPC_URLS_560048, defaultChain: parseInt(process.env.DEFAULT_CHAIN), supportedChains: process.env?.SUPPORTED_CHAINS?.split(',').map((chainId) => parseInt(chainId, 10), From b6ca8da010c2761272095666641191de8b0773b3 Mon Sep 17 00:00:00 2001 From: mateumiralles Date: Tue, 8 Apr 2025 11:22:59 +0200 Subject: [PATCH 57/58] fix: faqs re-organized --- faq/{ => holesky}/notifications-1.md | 0 faq/{ => holesky}/notifications-2.md | 0 faq/{ => holesky}/notifications-3.md | 0 faq/{ => holesky}/performance-1.md | 0 faq/{ => holesky}/performance-2.md | 0 faq/{ => holesky}/performance-3.md | 0 faq/{ => holesky}/performance-4.md | 0 faq/{ => holesky}/performance-5.md | 0 .../notifications-1.md} | 0 .../notifications-2.md} | 0 .../notifications-3.md} | 0 .../performance-1.md} | 0 .../performance-2.md} | 0 faq/hoodi/performance-3.md | 5 +++++ .../performance-4.md} | 0 .../performance-5.md} | 0 faq/mainnet/notifications-1.md | 9 +++++++++ faq/mainnet/notifications-2.md | 7 +++++++ faq/mainnet/notifications-3.md | 10 ++++++++++ faq/mainnet/performance-1.md | 7 +++++++ faq/mainnet/performance-2.md | 5 +++++ faq/mainnet/performance-3.md | 5 +++++ faq/mainnet/performance-4.md | 5 +++++ faq/mainnet/performance-5.md | 5 +++++ faq/testnet-performance-3.md | 5 ----- 25 files changed, 58 insertions(+), 5 deletions(-) rename faq/{ => holesky}/notifications-1.md (100%) rename faq/{ => holesky}/notifications-2.md (100%) rename faq/{ => holesky}/notifications-3.md (100%) rename faq/{ => holesky}/performance-1.md (100%) rename faq/{ => holesky}/performance-2.md (100%) rename faq/{ => holesky}/performance-3.md (100%) rename faq/{ => holesky}/performance-4.md (100%) rename faq/{ => holesky}/performance-5.md (100%) rename faq/{testnet-notifications-1.md => hoodi/notifications-1.md} (100%) rename faq/{testnet-notifications-2.md => hoodi/notifications-2.md} (100%) rename faq/{testnet-notifications-3.md => hoodi/notifications-3.md} (100%) rename faq/{testnet-performance-1.md => hoodi/performance-1.md} (100%) rename faq/{testnet-performance-2.md => hoodi/performance-2.md} (100%) create mode 100644 faq/hoodi/performance-3.md rename faq/{testnet-performance-4.md => hoodi/performance-4.md} (100%) rename faq/{testnet-performance-5.md => hoodi/performance-5.md} (100%) create mode 100644 faq/mainnet/notifications-1.md create mode 100644 faq/mainnet/notifications-2.md create mode 100644 faq/mainnet/notifications-3.md create mode 100644 faq/mainnet/performance-1.md create mode 100644 faq/mainnet/performance-2.md create mode 100644 faq/mainnet/performance-3.md create mode 100644 faq/mainnet/performance-4.md create mode 100644 faq/mainnet/performance-5.md delete mode 100644 faq/testnet-performance-3.md diff --git a/faq/notifications-1.md b/faq/holesky/notifications-1.md similarity index 100% rename from faq/notifications-1.md rename to faq/holesky/notifications-1.md diff --git a/faq/notifications-2.md b/faq/holesky/notifications-2.md similarity index 100% rename from faq/notifications-2.md rename to faq/holesky/notifications-2.md diff --git a/faq/notifications-3.md b/faq/holesky/notifications-3.md similarity index 100% rename from faq/notifications-3.md rename to faq/holesky/notifications-3.md diff --git a/faq/performance-1.md b/faq/holesky/performance-1.md similarity index 100% rename from faq/performance-1.md rename to faq/holesky/performance-1.md diff --git a/faq/performance-2.md b/faq/holesky/performance-2.md similarity index 100% rename from faq/performance-2.md rename to faq/holesky/performance-2.md diff --git a/faq/performance-3.md b/faq/holesky/performance-3.md similarity index 100% rename from faq/performance-3.md rename to faq/holesky/performance-3.md diff --git a/faq/performance-4.md b/faq/holesky/performance-4.md similarity index 100% rename from faq/performance-4.md rename to faq/holesky/performance-4.md diff --git a/faq/performance-5.md b/faq/holesky/performance-5.md similarity index 100% rename from faq/performance-5.md rename to faq/holesky/performance-5.md diff --git a/faq/testnet-notifications-1.md b/faq/hoodi/notifications-1.md similarity index 100% rename from faq/testnet-notifications-1.md rename to faq/hoodi/notifications-1.md diff --git a/faq/testnet-notifications-2.md b/faq/hoodi/notifications-2.md similarity index 100% rename from faq/testnet-notifications-2.md rename to faq/hoodi/notifications-2.md diff --git a/faq/testnet-notifications-3.md b/faq/hoodi/notifications-3.md similarity index 100% rename from faq/testnet-notifications-3.md rename to faq/hoodi/notifications-3.md diff --git a/faq/testnet-performance-1.md b/faq/hoodi/performance-1.md similarity index 100% rename from faq/testnet-performance-1.md rename to faq/hoodi/performance-1.md diff --git a/faq/testnet-performance-2.md b/faq/hoodi/performance-2.md similarity index 100% rename from faq/testnet-performance-2.md rename to faq/hoodi/performance-2.md diff --git a/faq/hoodi/performance-3.md b/faq/hoodi/performance-3.md new file mode 100644 index 00000000..1b5c7d79 --- /dev/null +++ b/faq/hoodi/performance-3.md @@ -0,0 +1,5 @@ +--- +title: How often is the data updated? +--- + +The Lido CSM team distributes a new report every 28 days, so it can take up to almost a monthly delay when checking your current performance compared to other Lido operators. diff --git a/faq/testnet-performance-4.md b/faq/hoodi/performance-4.md similarity index 100% rename from faq/testnet-performance-4.md rename to faq/hoodi/performance-4.md diff --git a/faq/testnet-performance-5.md b/faq/hoodi/performance-5.md similarity index 100% rename from faq/testnet-performance-5.md rename to faq/hoodi/performance-5.md diff --git a/faq/mainnet/notifications-1.md b/faq/mainnet/notifications-1.md new file mode 100644 index 00000000..0b8df581 --- /dev/null +++ b/faq/mainnet/notifications-1.md @@ -0,0 +1,9 @@ +--- +title: Why are notifications crucial? +--- + +Notifications are essential for staying informed about critical events within the Lido CSM protocol. By receiving alerts about exit requests, deposits, penalties, slashing incidents, and smart contract events, you can proactively manage your staking operations and address issues promptly. + +Staying informed helps reduce risks while maintaining transparency and control over your activities, ensuring smooth and efficient participation in the protocol. + +Learn more about this notifications in [our documentation](https://docs.dappnode.io/docs/user/staking/ethereum/lsd-pools/lido/notifications). diff --git a/faq/mainnet/notifications-2.md b/faq/mainnet/notifications-2.md new file mode 100644 index 00000000..d307dcbb --- /dev/null +++ b/faq/mainnet/notifications-2.md @@ -0,0 +1,7 @@ +--- +title: How to Get Your Telegram User ID +--- + +1. Open [Telegram](https://web.telegram.org/a/) and search for [`@userinfobot`](https://web.telegram.org/a/#52504489) or [`@raw_data_bot`](https://web.telegram.org/a/#1533228735). +2. Start a chat with the bot by clicking Start. +3. The bot will reply with your Telegram ID. diff --git a/faq/mainnet/notifications-3.md b/faq/mainnet/notifications-3.md new file mode 100644 index 00000000..db5fa1da --- /dev/null +++ b/faq/mainnet/notifications-3.md @@ -0,0 +1,10 @@ +--- +title: How to Create a Telegram Bot and Get the Bot Token +--- + +1. Open Telegram and search for [`@BotFather`](https://web.telegram.org/a/#93372553). +2. Start a chat with BotFather and type `/newbot`. +3. Follow the instructions to name your bot and choose a username (must end with "bot"). +4. Once created, BotFather will send you the bot token. + - Example: `123456789:ABCDefghIJKLMNOPQRSTuvwxYZ`. +5. Open the chat with your bot and clib the "`Start`" button. diff --git a/faq/mainnet/performance-1.md b/faq/mainnet/performance-1.md new file mode 100644 index 00000000..5b60d56e --- /dev/null +++ b/faq/mainnet/performance-1.md @@ -0,0 +1,7 @@ +--- +title: What is the Lido threshold? +--- + +The Lido threshold is the value that determines whether a validator should receive rewards or not. It is calculated with the average of all the efficiencies (attestation rates) of all validators. + +Validators with an efficiency higher than the threshold will get rewards, while those below it won’t. diff --git a/faq/mainnet/performance-2.md b/faq/mainnet/performance-2.md new file mode 100644 index 00000000..794b1e49 --- /dev/null +++ b/faq/mainnet/performance-2.md @@ -0,0 +1,5 @@ +--- +title: Where does the data come from? +--- + +We obtain the performance data of all Lido operators through its Smart Contract. The Lido CSM team distributes reports from all validators via IPFS hashes and Lido CSM package proccess it. Since this data is provided by Lido, is crucial in determining whether your validators qualify for rewards. diff --git a/faq/mainnet/performance-3.md b/faq/mainnet/performance-3.md new file mode 100644 index 00000000..1b5c7d79 --- /dev/null +++ b/faq/mainnet/performance-3.md @@ -0,0 +1,5 @@ +--- +title: How often is the data updated? +--- + +The Lido CSM team distributes a new report every 28 days, so it can take up to almost a monthly delay when checking your current performance compared to other Lido operators. diff --git a/faq/mainnet/performance-4.md b/faq/mainnet/performance-4.md new file mode 100644 index 00000000..e3bcc0a6 --- /dev/null +++ b/faq/mainnet/performance-4.md @@ -0,0 +1,5 @@ +--- +title: Where is this data stored? +--- + +The data collected from the Lido Smart Contract is stored in the Dappnode Lido package. Note that this data will include all samples from the validator; so the historical data from before the installation will be available. diff --git a/faq/mainnet/performance-5.md b/faq/mainnet/performance-5.md new file mode 100644 index 00000000..98eb5ad8 --- /dev/null +++ b/faq/mainnet/performance-5.md @@ -0,0 +1,5 @@ +--- +title: Is the data accurate? +--- + +To calculate the efficiency, which is used to compare with the Lido threshold, we rely on data from the Lido Smart Contract. This source is considered 100% accurate since its the data that will be used by Lido when allocating the rewards. diff --git a/faq/testnet-performance-3.md b/faq/testnet-performance-3.md deleted file mode 100644 index 75d4a96a..00000000 --- a/faq/testnet-performance-3.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: How often is the data updated? ---- - -The Lido CSM team distributes a new report every 7 days, so it can take up to a weekly delay when checking your current performance compared to other Lido operators. From d7a2d8b7afcdcb36ccd9f269b6624d37c5bbca09 Mon Sep 17 00:00:00 2001 From: mateumiralles Date: Tue, 15 Apr 2025 11:10:50 +0200 Subject: [PATCH 58/58] fix: use dappnode custom hooks --- dappnode/hooks/use-node-operators-fetcher-from-events-api.ts | 2 ++ features/dashboard/bond/last-rewards.tsx | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/dappnode/hooks/use-node-operators-fetcher-from-events-api.ts b/dappnode/hooks/use-node-operators-fetcher-from-events-api.ts index 1703d7b7..ab5e7b8d 100644 --- a/dappnode/hooks/use-node-operators-fetcher-from-events-api.ts +++ b/dappnode/hooks/use-node-operators-fetcher-from-events-api.ts @@ -66,9 +66,11 @@ const restoreEvents = ( return mergeRoles(prev, { id, manager: isUserAddress(e.newAddress), + rewards: false, }); case 'NodeOperatorRewardAddressChanged': return mergeRoles(prev, { + manager: false, id, rewards: isUserAddress(e.newAddress), }); diff --git a/features/dashboard/bond/last-rewards.tsx b/features/dashboard/bond/last-rewards.tsx index 673e4d36..d53e139c 100644 --- a/features/dashboard/bond/last-rewards.tsx +++ b/features/dashboard/bond/last-rewards.tsx @@ -19,7 +19,6 @@ import { import { FaqElement } from 'shared/components/faq/styles'; import { useLastOperatorRewards, - useLastRewrdsTx, useNodeOperatorInfo, useRewardsFrame, useSharesToSteth, @@ -34,6 +33,7 @@ import { RowHeader, RowTitle, } from './styles'; +import { useLastRewrdsTx } from 'dappnode/hooks/useLastRewardsFrame-api'; // DAPPNODE export const LastRewards: FC = () => { const { data: lastRewards, initialLoading: isLoading } =