From 9e55b052cdd19249f59cba4a6ac816a9b168b9a7 Mon Sep 17 00:00:00 2001 From: Rami Date: Sun, 23 Mar 2025 22:54:53 -0300 Subject: [PATCH 01/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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 ;