From 9e55b052cdd19249f59cba4a6ac816a9b168b9a7 Mon Sep 17 00:00:00 2001 From: Rami Date: Sun, 23 Mar 2025 22:54:53 -0300 Subject: [PATCH 001/418] 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 002/418] 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 003/418] 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 004/418] 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 005/418] 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 006/418] 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 007/418] 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 008/418] 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 009/418] 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 010/418] 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 011/418] 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 012/418] 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 013/418] 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 014/418] 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 015/418] 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 016/418] 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 017/418] 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 018/418] 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 019/418] 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 020/418] 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 021/418] 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 022/418] 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 023/418] 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 024/418] 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 025/418] 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 026/418] 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 027/418] 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 4e2b8247e175cf136fd19a3823e4dfffecb5b9bc Mon Sep 17 00:00:00 2001 From: exromany Date: Tue, 25 Mar 2025 00:12:52 +0700 Subject: [PATCH 028/418] fix: last rewards: show zero instead N/A --- features/dashboard/bond/last-rewards.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/features/dashboard/bond/last-rewards.tsx b/features/dashboard/bond/last-rewards.tsx index 7d0b5b68..74a0e068 100644 --- a/features/dashboard/bond/last-rewards.tsx +++ b/features/dashboard/bond/last-rewards.tsx @@ -28,6 +28,7 @@ import { RowHeader, RowTitle, } from './styles'; +import { Zero } from '@ethersproject/constants'; export const LastRewards: FC = () => { const { data: lastRewards, initialLoading: isLoading } = @@ -66,7 +67,7 @@ export const LastRewards: FC = () => { : undefined} /> From 62083aed99fe17bb9fef832ca8c984951208c980 Mon Sep 17 00:00:00 2001 From: exromany Date: Tue, 25 Mar 2025 01:13:57 +0700 Subject: [PATCH 029/418] 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 030/418] 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 031/418] chore: hide setups on testnet --- consts/urls.ts | 1 + features/surveys/surveys-home-page.tsx | 4 +-- .../surveys/surveys-home/surverys-home.tsx | 25 ++++++++++++++----- pages/surveys/[[...slug]].tsx | 2 ++ 4 files changed, 24 insertions(+), 8 deletions(-) diff --git a/consts/urls.ts b/consts/urls.ts index 35c5d810..b738260f 100644 --- a/consts/urls.ts +++ b/consts/urls.ts @@ -26,6 +26,7 @@ export const PATH = { SURVEYS_EXPERIENCE: '/surveys/experience', SURVEYS_HOW_DID_YOU_LEARN_CSM: '/surveys/learn-csm', SURVEYS_SETUP: '/surveys/setup', + SURVEYS_ALL: '/surveys/all', }; export type PATH = (typeof PATH)[keyof typeof PATH]; diff --git a/features/surveys/surveys-home-page.tsx b/features/surveys/surveys-home-page.tsx index 09ddb197..778f513c 100644 --- a/features/surveys/surveys-home-page.tsx +++ b/features/surveys/surveys-home-page.tsx @@ -4,10 +4,10 @@ import { NoSSRWrapper } from 'shared/components'; import { Layout } from 'shared/layout'; import { SurveysHome } from './surveys-home/surverys-home'; -export const SurveysHomePage: FC = () => ( +export const SurveysHomePage: FC<{ all?: boolean }> = ({ all }) => ( - + ); diff --git a/features/surveys/surveys-home/surverys-home.tsx b/features/surveys/surveys-home/surverys-home.tsx index 21e5f9c0..64c982e8 100644 --- a/features/surveys/surveys-home/surverys-home.tsx +++ b/features/surveys/surveys-home/surverys-home.tsx @@ -12,8 +12,12 @@ import { useSurveysSWR } from '../shared/use-surveys-swr'; import { useConfirmEraseModal } from './confirm-erase-modal'; import { Divider, Plus, Text } from '@lidofinance/lido-ui'; import { SetupsKeys, Summary } from '../types'; +import { CHAINS } from 'consts/chains'; +import { getConfig } from 'config'; -export const SurveysHome: FC = () => { +const { defaultChain } = getConfig(); + +export const SurveysHome: FC<{ all?: boolean }> = ({ all }) => { const { data, isLoading, remove } = useSurveysSWR('summary'); const { data: keys, mutate: mutateKeys } = useSurveysSWR('setups/keys'); @@ -27,6 +31,18 @@ export const SurveysHome: FC = () => { } }, [confirmModal, mutateKeys, remove]); + const showErase = !!( + data?.contacts || + data?.experience || + data?.howDidYouLearnCsm || + (data?.setups && data.setups.length > 0) + ); + const showSetups = !!( + (all || defaultChain === CHAINS.Mainnet) && + keys && + (keys.total > 0 || keys.filled > 0) + ); + return ( { - {keys && (keys.total > 0 || keys.filled > 0) && ( + {showSetups && ( { )} - {(data?.contacts || - data?.experience || - data?.howDidYouLearnCsm || - (data?.setups && data.setups.length > 0)) && ( + {showErase && ( <> diff --git a/pages/surveys/[[...slug]].tsx b/pages/surveys/[[...slug]].tsx index 22cfe801..f58a423b 100644 --- a/pages/surveys/[[...slug]].tsx +++ b/pages/surveys/[[...slug]].tsx @@ -32,6 +32,8 @@ const Page = () => { return ; case PATH.SURVEYS_SETUP: return ; + case PATH.SURVEYS_ALL: + return ; default: return ; From a0673973615fe54d38fafb2956a5b79727220b7f Mon Sep 17 00:00:00 2001 From: exromany Date: Tue, 25 Mar 2025 23:07:11 +0700 Subject: [PATCH 032/418] chore: return etherscan on hoodi --- faq/hoodi/keys-1.md | 4 ++-- .../external-icon-link/etherscan-address-link.tsx | 10 +--------- .../components/tx-link-etherscan/tx-link-etherscan.tsx | 10 +--------- 3 files changed, 4 insertions(+), 20 deletions(-) diff --git a/faq/hoodi/keys-1.md b/faq/hoodi/keys-1.md index 08ec2b0e..017eaffc 100644 --- a/faq/hoodi/keys-1.md +++ b/faq/hoodi/keys-1.md @@ -6,8 +6,8 @@ A detailed guide on preparing all the validation tools for CSM can be found [her A shorter flow of setting up a CSM validator for **testnet** looks as follows: -1. [Generate new validator keys](https://dvt-homestaker.stakesaurus.com/keystore-generation-and-mev-boost/validator-key-generation) setting the `withdrawal_address` to the [Lido Withdrawal Vault](https://hoodi.cloud.blockscout.com/address/0x4473dCDDbf77679A643BdB654dbd86D67F8d32f2) on **Hoodi:** [`0x4473dCDDbf77679A643BdB654dbd86D67F8d32f2`](https://hoodi.cloud.blockscout.com/address/0x4473dCDDbf77679A643BdB654dbd86D67F8d32f2) and specify the deposit amount of 32 ETH (do **NOT** make a deposit) -2. [Configure your validator client](https://dvt-homestaker.stakesaurus.com/native-solo-staking-setup/validator-client-setup) (and/or beacon node) setting the `fee_recipient` flag to the designated fee recipient address (Lido Execution Layer Rewards Vault) on **Hoodi:** [`0x9b108015fe433F173696Af3Aa0CF7CDb3E104258`](https://hoodi.cloud.blockscout.com/address/0x9b108015fe433F173696Af3Aa0CF7CDb3E104258) and import the newly generated CSM keystores +1. [Generate new validator keys](https://dvt-homestaker.stakesaurus.com/keystore-generation-and-mev-boost/validator-key-generation) setting the `withdrawal_address` to the [Lido Withdrawal Vault](https://hoodi.etherscan.io/address/0x4473dCDDbf77679A643BdB654dbd86D67F8d32f2) on **Hoodi:** [`0x4473dCDDbf77679A643BdB654dbd86D67F8d32f2`](https://hoodi.etherscan.io/address/0x4473dCDDbf77679A643BdB654dbd86D67F8d32f2) and specify the deposit amount of 32 ETH (do **NOT** make a deposit) +2. [Configure your validator client](https://dvt-homestaker.stakesaurus.com/native-solo-staking-setup/validator-client-setup) (and/or beacon node) setting the `fee_recipient` flag to the designated fee recipient address (Lido Execution Layer Rewards Vault) on **Hoodi:** [`0x9b108015fe433F173696Af3Aa0CF7CDb3E104258`](https://hoodi.etherscan.io/address/0x9b108015fe433F173696Af3Aa0CF7CDb3E104258) and import the newly generated CSM keystores 3. **Do not setup any MEV-Boost** 4. [Upload the newly generated deposit data](https://dvt-homestaker.stakesaurus.com/bonded-validators-setup/lido-csm/upload-remove-view-validator-keys) file pertaining to your CSM keystores onto [the Lido CSM Widget](https://csm.testnet.fi/) and provide the required bond amount in Hoodi ETH/stETH/wstETH. Before uploading, make sure that nodes are synced, running, and ready for the validator activation. 5. Wait for your CSM validator keys to be deposited through the protocol and make sure your node remains online in the meantime! diff --git a/shared/components/external-icon-link/etherscan-address-link.tsx b/shared/components/external-icon-link/etherscan-address-link.tsx index 87665d93..40bf10af 100644 --- a/shared/components/external-icon-link/etherscan-address-link.tsx +++ b/shared/components/external-icon-link/etherscan-address-link.tsx @@ -4,19 +4,11 @@ import { MATOMO_CLICK_EVENTS_TYPES } from 'consts/matomo-click-events'; import { FC } from 'react'; import { useAccount } from 'shared/hooks'; import { MatomoLink } from '../matomo-link/matomo-link'; -import { CHAINS } from '@lido-sdk/constants'; type Props = { address: string; }; -const getExplorerAddressLink = (chainId: number, address: string) => { - if (chainId === CHAINS.Hoodi) { - return `https://hoodi.cloud.blockscout.com/address/${address}`; - } - return getEtherscanAddressLink(chainId, address); -}; - export const EtherscanAddressLink: FC = ({ address }) => { const { chainId } = useAccount(); @@ -24,7 +16,7 @@ export const EtherscanAddressLink: FC = ({ address }) => { return ( diff --git a/shared/components/tx-link-etherscan/tx-link-etherscan.tsx b/shared/components/tx-link-etherscan/tx-link-etherscan.tsx index 4f1a21cb..cd5c881c 100644 --- a/shared/components/tx-link-etherscan/tx-link-etherscan.tsx +++ b/shared/components/tx-link-etherscan/tx-link-etherscan.tsx @@ -3,20 +3,12 @@ import { getEtherscanTxLink } from '@lido-sdk/helpers'; import { MatomoLink } from '../matomo-link/matomo-link'; import { MATOMO_CLICK_EVENTS_TYPES } from 'consts/matomo-click-events'; import { FC } from 'react'; -import { CHAINS } from '@lido-sdk/constants'; type TxLinkEtherscanProps = { text?: string; txHash?: string | null; }; -const getExplorerTxLink = (chainId: number, hash: string) => { - if (chainId === CHAINS.Hoodi) { - return `https://hoodi.cloud.blockscout.com/tx/${hash}`; - } - return getEtherscanTxLink(chainId, hash); -}; - export const TxLinkEtherscan: FC = ({ txHash, text = 'View on Etherscan', @@ -27,7 +19,7 @@ export const TxLinkEtherscan: FC = ({ return ( {text} From 2c6467082e50cc0956024e4ff25de1fcdf2d35a8 Mon Sep 17 00:00:00 2001 From: exromany Date: Tue, 25 Mar 2025 23:09:33 +0700 Subject: [PATCH 033/418] chore: hoodi beaconcha.in key link --- consts/external-links.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/consts/external-links.ts b/consts/external-links.ts index c3952cae..a97bc430 100644 --- a/consts/external-links.ts +++ b/consts/external-links.ts @@ -92,7 +92,7 @@ export const EXTERNAL_LINKS_BY_NETWORK: Record = feesMonitoring: 'https://fees-monitoring-hoodi.testnet.fi', operatorsWidget: 'https://operators-hoodi.testnet.fi', - beaconchain: '', + beaconchain: 'https://hoodi.beaconcha.in', beaconchainDashboard: '', ratedExplorer: 'https://explorer.rated.network', subscribeEvents: 'https://docs.lido.fi/staking-modules/csm/guides/events', From 05e85a92e8e5036cb6246ad31be52999fd260101 Mon Sep 17 00:00:00 2001 From: exromany Date: Tue, 25 Mar 2025 23:14:13 +0700 Subject: [PATCH 034/418] chore: hoodi disable surveys banner --- shared/hooks/use-surveys-call.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/shared/hooks/use-surveys-call.ts b/shared/hooks/use-surveys-call.ts index d85bd4f7..3c16e02a 100644 --- a/shared/hooks/use-surveys-call.ts +++ b/shared/hooks/use-surveys-call.ts @@ -1,7 +1,11 @@ +import { getConfig } from 'config'; +import { CHAINS } from 'consts/chains'; import { isBefore, parseISO } from 'date-fns'; +const { defaultChain } = getConfig(); + export const useSurveysCall = () => { const endOfSurvey = parseISO('2025-04-01T00:00Z'); const today = new Date(); - return isBefore(today, endOfSurvey); + return defaultChain === CHAINS.Mainnet && isBefore(today, endOfSurvey); }; From c285f4ca33cc4ac9c68ec8702f4a9f22596bd630 Mon Sep 17 00:00:00 2001 From: exromany Date: Wed, 26 Mar 2025 18:47:56 +0700 Subject: [PATCH 035/418] chore: add bond text --- features/add-bond/add-bond-form/controls/info.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/features/add-bond/add-bond-form/controls/info.tsx b/features/add-bond/add-bond-form/controls/info.tsx index 54bf12f1..1e275999 100644 --- a/features/add-bond/add-bond-form/controls/info.tsx +++ b/features/add-bond/add-bond-form/controls/info.tsx @@ -1,4 +1,4 @@ -import { BOND_EXCESS, BOND_INSUFFICIENT } from 'consts/text'; +import { BOND_INSUFFICIENT } from 'consts/text'; import { TOKENS } from 'consts/tokens'; import { FC } from 'react'; import { Latice, MatomoLink, Stack, TitledAmount } from 'shared/components'; @@ -13,11 +13,11 @@ export const Info: FC = () => { { {bond?.isInsufficient ? (

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

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

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

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

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

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

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

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

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

    ; + cache.set(cacheKey, provider); + } + + if (pollingInterval) { + provider.pollingInterval = pollingInterval; + } + + return provider; + }; +}; + +const logger = new Logger('StaticJsonRpcBatchProvider/1.0'); + +class StaticJsonRpcBatchProvider extends JsonRpcProvider { + async detectNetwork(): Promise { + let network = this.network; + + if (network == null) { + network = await super.detectNetwork(); + + if (!network) { + logger.throwError( + 'no network detected', + Logger.errors.UNKNOWN_ERROR, + {}, + ); + } + + // If still not set, set it + if (this._network == null) { + // A static network does not support "any" + defineReadOnly(this, '_network', network); + + this.emit('network', network, null); + } + } + + return network; + } + + _pendingBatchAggregator: NodeJS.Timer | null = null; + _pendingBatch: Array<{ + request: { method: string; params: Array; id: number; jsonrpc: '2.0' }; + resolve: (result: any) => void; + reject: (error: Error) => void; + }> = []; + + send(method: string, params: Array): Promise { + const request = { + method: method, + params: params, + id: this._nextId++, + jsonrpc: '2.0', + }; + + const inflightRequest: any = { request, resolve: null, reject: null }; + + const promise = new Promise((resolve, reject) => { + inflightRequest.resolve = resolve; + inflightRequest.reject = reject; + }); + + this._pendingBatch.push(inflightRequest); + + // eslint-disable-next-line unicorn/consistent-function-scoping + const runAggregator = () => { + if (this._pendingBatchAggregator) { + return; + } + // Schedule batch for next event loop + short duration + this._pendingBatchAggregator = setTimeout(() => { + // Get teh current batch and clear it, so new requests + // go into the next batch + const batch = this._pendingBatch.splice(0, MAX_BATCH_SIZE); + this._pendingBatchAggregator = null; + if (batch.length === 0) { + return; + } + if (this._pendingBatch.length > 0) { + runAggregator(); + } + + // Get the request as an array of requests + const request = batch.map((inflight) => inflight.request); + + this.emit('debug', { + action: 'requestBatch', + request: deepCopy(request), + provider: this, + }); + + return fetchJson(this.connection, JSON.stringify(request)).then( + (result) => { + this.emit('debug', { + action: 'response', + request: request, + response: result, + provider: this, + }); + + // For each result, feed it to the correct Promise, depending + // on whether it was a success or error + batch.forEach((inflightRequest, index) => { + const payload = result[index]; + if (payload.error) { + const error = new Error(payload.error.message); + (error).code = payload.error.code; + (error).data = payload.error.data; + inflightRequest.reject(error); + } else { + inflightRequest.resolve(payload.result); + } + }); + }, + (error) => { + this.emit('debug', { + action: 'response', + error: error, + request: request, + provider: this, + }); + + batch.forEach((inflightRequest) => { + inflightRequest.reject(error); + }); + }, + ); + }, 10); + }; + + runAggregator(); + + return promise; + } +} + +export const getStaticRpcBatchProvider = createProviderGetter( + StaticJsonRpcBatchProvider, +); From b95ff4d6c0060f77560a4a7eadf98bce683cc2ca Mon Sep 17 00:00:00 2001 From: exromany Date: Thu, 3 Apr 2025 12:23:19 +0300 Subject: [PATCH 055/418] fix: optional page props --- pages/_app.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/_app.tsx b/pages/_app.tsx index 1b12e12b..ba2a6df1 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -35,7 +35,7 @@ const AppWrapper = (props: AppProps): JSX.Element => { return ( {/* see https://nextjs.org/docs/messages/no-document-viewport-meta */} From 3908c5777a120b3d05dad9632148c04ccb7ce203 Mon Sep 17 00:00:00 2001 From: exromany Date: Thu, 3 Apr 2025 19:37:40 +0300 Subject: [PATCH 056/418] fix: fail after disconnect on roles tab --- .../change-role-form/controls/info.tsx | 66 +++++++++---------- shared/hooks/use-show-rule.ts | 2 +- 2 files changed, 32 insertions(+), 36 deletions(-) diff --git a/features/change-role/change-role-form/controls/info.tsx b/features/change-role/change-role-form/controls/info.tsx index 7ead48fc..aefae92a 100644 --- a/features/change-role/change-role-form/controls/info.tsx +++ b/features/change-role/change-role-form/controls/info.tsx @@ -24,41 +24,37 @@ export const Info: FC = () => { address={currentAddress} /> - - - - {isPropose && ( - - Revoke - - )} - - } - address={proposedAddress} - /> - {proposedAddress && ( - <> - - Action required - - -

      -
    1. Connect to CSM UI with the proposed address
    2. -
    3. - Go to Roles tab → Inbox requests to confirm the change -
    4. -
    - - - )} - + {proposedAddress && ( + + + + {isPropose && ( + + Revoke + + )} + + } + address={proposedAddress} + /> + + Action required + + +
      +
    1. Connect to CSM UI with the proposed address
    2. +
    3. Go to Roles tab → Inbox requests to confirm the change
    4. +
    +
    +
    + )} ); diff --git a/shared/hooks/use-show-rule.ts b/shared/hooks/use-show-rule.ts index 2f259cce..289ddd42 100644 --- a/shared/hooks/use-show-rule.ts +++ b/shared/hooks/use-show-rule.ts @@ -39,7 +39,7 @@ export const useShowRule = () => { case 'NOT_NODE_OPERATOR': return !nodeOperator; case 'IS_NODE_OPERATOR': - return !!nodeOperator; + return !!nodeOperator && isConnectedWallet; case 'CAN_CREATE': return !!canCreateNO; case 'HAS_MANAGER_ROLE': From 9b190cb5a628c0b696058e148345c7bebd8a6c5e Mon Sep 17 00:00:00 2001 From: exromany Date: Thu, 3 Apr 2025 19:46:49 +0300 Subject: [PATCH 057/418] fix: wallet modal - view on etherscan for hoodi --- shared/wallet/wallet-modal/wallet-modal.tsx | 43 +++++++++++---------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/shared/wallet/wallet-modal/wallet-modal.tsx b/shared/wallet/wallet-modal/wallet-modal.tsx index 99b30fec..ab8f5ff7 100644 --- a/shared/wallet/wallet-modal/wallet-modal.tsx +++ b/shared/wallet/wallet-modal/wallet-modal.tsx @@ -1,29 +1,30 @@ -import { useCallback } from 'react'; import { + Address, ButtonIcon, - Modal, - Identicon, - External, Copy, - Address, + External, + Identicon, + Modal, } from '@lidofinance/lido-ui'; -import { useEtherscanOpen } from '@lido-sdk/react'; +import { useCallback } from 'react'; import { useConnectorInfo, useDisconnect } from 'reef-knot/core-react'; +import { getEtherscanAddressLink } from '@lido-sdk/helpers'; +import Link from 'next/link'; import type { ModalComponentType } from 'providers/modal-provider'; import { useAccount, useCopyToClipboard } from 'shared/hooks'; import { - WalletModalContentStyle, + WalletModalAccountStyle, + WalletModalActionsStyle, + WalletModalAddressStyle, WalletModalConnectedStyle, WalletModalConnectorStyle, + WalletModalContentStyle, WalletModalDisconnectStyle, - WalletModalAccountStyle, - WalletModalAddressStyle, - WalletModalActionsStyle, } from './styles'; export const WalletModal: ModalComponentType = ({ onClose, ...props }) => { - const { address } = useAccount(); + const { address, chainId } = useAccount(); const { connectorName } = useConnectorInfo(); const { disconnect } = useDisconnect(); @@ -32,7 +33,6 @@ export const WalletModal: ModalComponentType = ({ onClose, ...props }) => { }, [onClose]); const handleCopy = useCopyToClipboard(address ?? ''); - const handleEtherscan = useEtherscanOpen(address ?? '', 'address'); return ( @@ -76,15 +76,16 @@ export const WalletModal: ModalComponentType = ({ onClose, ...props }) => { > Copy address - } - size="xs" - variant="ghost" - > - View on Etherscan - + + } + size="xs" + variant="ghost" + > + View on Etherscan + + From 1c5c9b09973a0acef82e0447f9647ca56ea41070 Mon Sep 17 00:00:00 2001 From: exromany Date: Thu, 3 Apr 2025 09:31:21 +0300 Subject: [PATCH 058/418] fix: limit rpc batch size --- .gitignore | 1 + providers/sdk-legacy.tsx | 2 +- providers/web3.tsx | 2 +- utils/getStaticRpcBatchProvider.ts | 166 +++++++++++++++++++++++++++++ 4 files changed, 169 insertions(+), 2 deletions(-) create mode 100644 utils/getStaticRpcBatchProvider.ts diff --git a/.gitignore b/.gitignore index 0a71a893..c3d52525 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,4 @@ yarn-error.log* .idea /public/runtime/ +.qodo diff --git a/providers/sdk-legacy.tsx b/providers/sdk-legacy.tsx index 011cea22..cd448e9c 100644 --- a/providers/sdk-legacy.tsx +++ b/providers/sdk-legacy.tsx @@ -1,10 +1,10 @@ import React, { useEffect, useState } from 'react'; import { ProviderSDK } from '@lido-sdk/react'; -import { getStaticRpcBatchProvider } from '@lido-sdk/providers'; import { Web3Provider } from '@ethersproject/providers'; import { Chain, useAccount as useWagmiAccount } from 'wagmi'; import { mainnet } from 'wagmi/chains'; import { useAccount } from 'shared/hooks'; +import { getStaticRpcBatchProvider } from 'utils/getStaticRpcBatchProvider'; const POLLING_INTERVAL = 12_000; diff --git a/providers/web3.tsx b/providers/web3.tsx index 95079ebf..78ef8f8e 100644 --- a/providers/web3.tsx +++ b/providers/web3.tsx @@ -2,7 +2,6 @@ import { FC, PropsWithChildren, useMemo } from 'react'; import { ReefKnot, getConnectors, holesky } from 'reef-knot/core-react'; import { WagmiConfig, createClient, configureChains, Chain } from 'wagmi'; import * as wagmiChains from 'wagmi/chains'; -import { getStaticRpcBatchProvider } from '@lido-sdk/providers'; import { hoodi } from 'consts/hoodi'; import { useUserConfig } from 'config/user-config'; @@ -11,6 +10,7 @@ import { CHAINS } from 'consts/chains'; import { ConnectWalletModal } from 'shared/wallet/connect-wallet-modal'; import { SDKLegacyProvider } from './sdk-legacy'; +import { getStaticRpcBatchProvider } from 'utils/getStaticRpcBatchProvider'; const wagmiChainsArray = Object.values({ ...wagmiChains, hoodi, holesky }); diff --git a/utils/getStaticRpcBatchProvider.ts b/utils/getStaticRpcBatchProvider.ts new file mode 100644 index 00000000..73618a11 --- /dev/null +++ b/utils/getStaticRpcBatchProvider.ts @@ -0,0 +1,166 @@ +import { JsonRpcProvider, Network } from '@ethersproject/providers'; +import { CHAINS } from '@lido-sdk/constants'; +import { + deepCopy, + defineReadOnly, + fetchJson, + Logger, +} from 'ethers/lib/utils.js'; + +const MAX_BATCH_SIZE = 20; + +const createProviderGetter =

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

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

    ; + cache.set(cacheKey, provider); + } + + if (pollingInterval) { + provider.pollingInterval = pollingInterval; + } + + return provider; + }; +}; + +const logger = new Logger('StaticJsonRpcBatchProvider/1.0'); + +class StaticJsonRpcBatchProvider extends JsonRpcProvider { + async detectNetwork(): Promise { + let network = this.network; + + if (network == null) { + network = await super.detectNetwork(); + + if (!network) { + logger.throwError( + 'no network detected', + Logger.errors.UNKNOWN_ERROR, + {}, + ); + } + + // If still not set, set it + if (this._network == null) { + // A static network does not support "any" + defineReadOnly(this, '_network', network); + + this.emit('network', network, null); + } + } + + return network; + } + + _pendingBatchAggregator: NodeJS.Timer | null = null; + _pendingBatch: Array<{ + request: { method: string; params: Array; id: number; jsonrpc: '2.0' }; + resolve: (result: any) => void; + reject: (error: Error) => void; + }> = []; + + send(method: string, params: Array): Promise { + const request = { + method: method, + params: params, + id: this._nextId++, + jsonrpc: '2.0', + }; + + const inflightRequest: any = { request, resolve: null, reject: null }; + + const promise = new Promise((resolve, reject) => { + inflightRequest.resolve = resolve; + inflightRequest.reject = reject; + }); + + this._pendingBatch.push(inflightRequest); + + // eslint-disable-next-line unicorn/consistent-function-scoping + const runAggregator = () => { + if (this._pendingBatchAggregator) { + return; + } + // Schedule batch for next event loop + short duration + this._pendingBatchAggregator = setTimeout(() => { + // Get teh current batch and clear it, so new requests + // go into the next batch + const batch = this._pendingBatch.splice(0, MAX_BATCH_SIZE); + this._pendingBatchAggregator = null; + if (batch.length === 0) { + return; + } + if (this._pendingBatch.length > 0) { + runAggregator(); + } + + // Get the request as an array of requests + const request = batch.map((inflight) => inflight.request); + + this.emit('debug', { + action: 'requestBatch', + request: deepCopy(request), + provider: this, + }); + + return fetchJson(this.connection, JSON.stringify(request)).then( + (result) => { + this.emit('debug', { + action: 'response', + request: request, + response: result, + provider: this, + }); + + // For each result, feed it to the correct Promise, depending + // on whether it was a success or error + batch.forEach((inflightRequest, index) => { + const payload = result[index]; + if (payload.error) { + const error = new Error(payload.error.message); + (error).code = payload.error.code; + (error).data = payload.error.data; + inflightRequest.reject(error); + } else { + inflightRequest.resolve(payload.result); + } + }); + }, + (error) => { + this.emit('debug', { + action: 'response', + error: error, + request: request, + provider: this, + }); + + batch.forEach((inflightRequest) => { + inflightRequest.reject(error); + }); + }, + ); + }, 10); + }; + + runAggregator(); + + return promise; + } +} + +export const getStaticRpcBatchProvider = createProviderGetter( + StaticJsonRpcBatchProvider, +); From c177cb4981d0fc70aa2f0150699646e5575711ab Mon Sep 17 00:00:00 2001 From: exromany Date: Thu, 3 Apr 2025 12:23:19 +0300 Subject: [PATCH 059/418] fix: optional page props --- pages/_app.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/_app.tsx b/pages/_app.tsx index 1b12e12b..ba2a6df1 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -35,7 +35,7 @@ const AppWrapper = (props: AppProps): JSX.Element => { return ( {/* see https://nextjs.org/docs/messages/no-document-viewport-meta */} From 08d54a43756cce469497499015c1518aca074ec7 Mon Sep 17 00:00:00 2001 From: exromany Date: Sat, 29 Mar 2025 12:03:01 +0300 Subject: [PATCH 060/418] fix: nesting tag errors & address component refactor --- .../add-keys/controls/keys-confirm.tsx | 2 +- .../claim-bond-form/claim-bond-form-info.tsx | 32 +++----- .../confirm-custom-addresses-modal.tsx | 8 +- .../controls/keys-confirm.tsx | 2 +- features/dashboard/roles/proposed-address.tsx | 5 +- features/dashboard/roles/role-block.tsx | 5 +- .../required-bond-amount.tsx | 2 +- .../unlock-bond-form/controls/info.tsx | 4 +- shared/components/address/address.tsx | 80 ++++++++++++------- shared/components/address/styles.tsx | 19 ++--- shared/components/status-chip/style.ts | 1 + 11 files changed, 81 insertions(+), 79 deletions(-) diff --git a/features/add-keys/add-keys/controls/keys-confirm.tsx b/features/add-keys/add-keys/controls/keys-confirm.tsx index d604a073..62406950 100644 --- a/features/add-keys/add-keys/controls/keys-confirm.tsx +++ b/features/add-keys/add-keys/controls/keys-confirm.tsx @@ -11,7 +11,7 @@ export const KeysConfirm: FC = () => { return ( - + I confirm that:

    • diff --git a/features/claim-bond/claim-bond-form/claim-bond-form-info.tsx b/features/claim-bond/claim-bond-form/claim-bond-form-info.tsx index cba4552d..f0431b3e 100644 --- a/features/claim-bond/claim-bond-form/claim-bond-form-info.tsx +++ b/features/claim-bond/claim-bond-form/claim-bond-form-info.tsx @@ -2,15 +2,10 @@ import { Zero } from '@ethersproject/constants'; import { DataTable, DataTableRow } from '@lidofinance/lido-ui'; import { TOKENS } from 'consts/tokens'; import { useWatch } from 'react-hook-form'; -import { FormatToken } from 'shared/formatters'; -import styled from 'styled-components'; -import { ClaimBondFormInputType, useClaimBondFormData } from './context'; import { Address } from 'shared/components'; -import { - AddressContainerStyle, - AddressStyle, -} from 'shared/components/address/styles'; +import { FormatToken } from 'shared/formatters'; import { useBondWillReceive } from 'shared/hooks'; +import { ClaimBondFormInputType, useClaimBondFormData } from './context'; export const ClaimBondFormInfo = () => { const { rewardsAddress, rewards } = useClaimBondFormData(); @@ -31,10 +26,16 @@ export const ClaimBondFormInfo = () => { + <> Rewards Address ( -
      ) will receive - +
      + ) will receive + } help="The recipient of the claim is the Rewards address. You can change the Rewards address on the Roles tab" > @@ -48,14 +49,3 @@ export const ClaimBondFormInfo = () => { ); }; - -const AddressStyled = styled.div` - display: inline; - - ${AddressContainerStyle} { - display: inline-flex; - } - ${AddressStyle} { - font-weight: bold; - } -`; diff --git a/features/create-node-operator/submit-keys-form/confirm-custom-addresses-modal.tsx b/features/create-node-operator/submit-keys-form/confirm-custom-addresses-modal.tsx index c6feabbc..cdc30a7b 100644 --- a/features/create-node-operator/submit-keys-form/confirm-custom-addresses-modal.tsx +++ b/features/create-node-operator/submit-keys-form/confirm-custom-addresses-modal.tsx @@ -86,16 +86,12 @@ const CustomAddresses: FC> = ({ Rewards Address - -
      - +
      Manager Address - -
      - +
      {extendedManagerPermissions && } diff --git a/features/create-node-operator/submit-keys-form/controls/keys-confirm.tsx b/features/create-node-operator/submit-keys-form/controls/keys-confirm.tsx index 2b0ace21..708f9222 100644 --- a/features/create-node-operator/submit-keys-form/controls/keys-confirm.tsx +++ b/features/create-node-operator/submit-keys-form/controls/keys-confirm.tsx @@ -11,7 +11,7 @@ export const KeysConfirm: FC = () => { return ( - + I confirm that:
      • diff --git a/features/dashboard/roles/proposed-address.tsx b/features/dashboard/roles/proposed-address.tsx index c0863244..fdb5b773 100644 --- a/features/dashboard/roles/proposed-address.tsx +++ b/features/dashboard/roles/proposed-address.tsx @@ -1,7 +1,6 @@ import { FC } from 'react'; import { Address, Warning } from 'shared/components'; import { RoleBlockProposed } from './styles'; -import { Text } from '@lidofinance/lido-ui'; type Props = { address?: string }; @@ -11,9 +10,7 @@ export const ProposedAddress: FC = ({ address }) => { return ( - -
        - +
        ); }; diff --git a/features/dashboard/roles/role-block.tsx b/features/dashboard/roles/role-block.tsx index 75eddc24..5b3fb997 100644 --- a/features/dashboard/roles/role-block.tsx +++ b/features/dashboard/roles/role-block.tsx @@ -2,7 +2,6 @@ import { FC } from 'react'; import { Address, Stack } from 'shared/components'; import { ProposedAddress } from './proposed-address'; import { Chip, RoleBlockWrapper, RoleTitle } from './styles'; -import { Text } from '@lidofinance/lido-ui'; type RoleBlockProps = { title: string; @@ -23,9 +22,7 @@ export const RoleBlock: FC = ({ {title} {isYou && You} - -
        - +
        ); 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 068eaea5..df705971 100644 --- a/features/starter-pack/stacter-pack-section/required-bond-amount.tsx +++ b/features/starter-pack/stacter-pack-section/required-bond-amount.tsx @@ -20,7 +20,7 @@ export const RequiredBondAmount: FC = () => { return ( <> {curveLoading || curveInfoLoading ? ( - + ) : ( diff --git a/features/unlock-bond/unlock-bond-form/controls/info.tsx b/features/unlock-bond/unlock-bond-form/controls/info.tsx index 10b1263c..eb4d4be5 100644 --- a/features/unlock-bond/unlock-bond-form/controls/info.tsx +++ b/features/unlock-bond/unlock-bond-form/controls/info.tsx @@ -18,7 +18,7 @@ export const Info: FC = () => { token={TOKENS.ETH} /> {lockedBond?.gt(0) && ( -

        +

        EL reward stealing {' '} @@ -46,7 +46,7 @@ export const Info: FC = () => { to avoid the further penalties.
      -

      + )}
      diff --git a/shared/components/address/address.tsx b/shared/components/address/address.tsx index 5a04b040..d7f1a064 100644 --- a/shared/components/address/address.tsx +++ b/shared/components/address/address.tsx @@ -1,38 +1,64 @@ -import { AddressProps, Identicon, Tooltip } from '@lidofinance/lido-ui'; -import { FC, ReactNode } from 'react'; +import { + AddressProps, + Address as AddressComponent, + Identicon, + Text, + Tooltip, +} from '@lidofinance/lido-ui'; +import { ComponentProps, FC, ReactNode } from 'react'; import { EtherscanAddressLink } from '../external-icon-link'; -import { AddressStyle, AddressContainerStyle } from './styles'; +import { AddressContainerStyle } from './styles'; type Props = { showIcon?: boolean; - bold?: boolean; + big?: boolean; link?: ReactNode; -} & Partial; +} & Pick, 'weight' | 'size' | 'color'> & + Partial; export const Address: FC = ({ address = '', symbols = 6, showIcon = false, - bold = false, + big = false, + weight, + size = 'xs', + color, link, -}) => ( - <> - {address && ( - - {showIcon && } - {symbols === 0 ? ( - - ) : ( - - - - )} - {link ?? } - - )} - -); +}) => { + const component = ( + + + + ); + return ( + <> + {address && ( + + {showIcon && } + {symbols === 0 ? ( + component + ) : ( + + {component} + + )} + {link ?? } + + )} + + ); +}; diff --git a/shared/components/address/styles.tsx b/shared/components/address/styles.tsx index 7f62a0c3..22e4dcce 100644 --- a/shared/components/address/styles.tsx +++ b/shared/components/address/styles.tsx @@ -1,30 +1,25 @@ -import { Address } from '@lidofinance/lido-ui'; import styled, { css } from 'styled-components'; import { LinkStyled } from '../matomo-link/styles'; import { StackStyle } from '../stack/style'; -export const AddressStyle = styled(Address)<{ $bold?: boolean }>` - ${({ $bold }) => - $bold && - css` - font-weight: bold; - `} -`; - export const AddressContainerStyle = styled(StackStyle).attrs({ $gap: 'xs', $align: 'center', -})<{ $bold?: boolean }>` +})<{ $big?: boolean }>` display: inline-flex; + > span > span { + font-weight: inherit; + } + ${LinkStyled} { svg { display: inline-flex; flex-shrink: 0; } - ${({ $bold }) => - !$bold && + ${({ $big }) => + !$big && css` &, &:visited { diff --git a/shared/components/status-chip/style.ts b/shared/components/status-chip/style.ts index b78d5dee..d36c0786 100644 --- a/shared/components/status-chip/style.ts +++ b/shared/components/status-chip/style.ts @@ -24,6 +24,7 @@ export const StatusStyle = styled.div<{ $variant?: Variants }>` width: fit-content; padding: 4px 12px; text-align: center; + white-space: nowrap; border-radius: ${({ theme }) => theme.borderRadiusesMap.xl}px; background: color-mix(in srgb, currentColor 15%, transparent); From e1cc1b9abe892a1949f97ee667952550830d7daa Mon Sep 17 00:00:00 2001 From: exromany Date: Sat, 29 Mar 2025 12:19:26 +0300 Subject: [PATCH 061/418] feat: navigation in side menu --- features/starter-pack/starter-pack.tsx | 18 ++++----- shared/components/logos/logos.tsx | 5 +-- shared/components/logos/styles.tsx | 4 ++ shared/hooks/use-show-rule.ts | 2 +- shared/layout/footer/footer.tsx | 11 +----- shared/layout/footer/styles.tsx | 38 +++++++++---------- shared/layout/header/components/logos.tsx | 14 +++++++ shared/layout/header/dummy-header.tsx | 27 ++++--------- shared/layout/header/header.tsx | 30 ++++++--------- shared/layout/header/styles.tsx | 26 ++++++++----- shared/layout/layout.tsx | 26 ++++++++----- shared/layout/main/main.tsx | 10 ----- shared/layout/main/styles.tsx | 19 ---------- .../components => }/navigation/navigation.tsx | 0 .../components => }/navigation/styles.tsx | 9 +++-- .../navigation/use-nav-items.tsx | 0 shared/layout/styles.tsx | 29 +++++++++++++- shared/navigate/switcher/styles.tsx | 3 +- 18 files changed, 137 insertions(+), 134 deletions(-) create mode 100644 shared/layout/header/components/logos.tsx delete mode 100644 shared/layout/main/main.tsx delete mode 100644 shared/layout/main/styles.tsx rename shared/layout/{header/components => }/navigation/navigation.tsx (100%) rename shared/layout/{header/components => }/navigation/styles.tsx (94%) rename shared/layout/{header/components => }/navigation/use-nav-items.tsx (100%) diff --git a/features/starter-pack/starter-pack.tsx b/features/starter-pack/starter-pack.tsx index cbfd3e41..48a5d1c5 100644 --- a/features/starter-pack/starter-pack.tsx +++ b/features/starter-pack/starter-pack.tsx @@ -2,12 +2,11 @@ import { Button } from '@lidofinance/lido-ui'; import { MATOMO_CLICK_EVENTS_TYPES } from 'consts/matomo-click-events'; import { PATH } from 'consts/urls'; import { TryCSM } from 'features/welcome/try-csm'; -import Link from 'next/link'; -import { FC, useCallback } from 'react'; +import { FC } from 'react'; import { Faq } from 'shared/components'; import { useCsmEarlyAdoption } from 'shared/hooks'; import { useCsmPaused, useCsmPublicRelease } from 'shared/hooks/useCsmStatus'; -import { trackMatomoEvent } from 'utils'; +import { LocalLink } from 'shared/navigate'; import { BannerOperatorCustomAddresses } from './banner-operator-custom-addresses'; import { ConsumedBanner } from './consumed-banner'; import { NotEligibleBanner } from './not-eligible-banner/not-eligible-banner'; @@ -19,15 +18,14 @@ export const StarterPack: FC = () => { const { data: isPublicRelease } = useCsmPublicRelease(); const { data: ea } = useCsmEarlyAdoption(); - const handleClick = useCallback(() => { - trackMatomoEvent(MATOMO_CLICK_EVENTS_TYPES.starterPackCreateNodeOperator); - }, []); - let content = ( - - - + + + ); diff --git a/shared/components/logos/logos.tsx b/shared/components/logos/logos.tsx index 00ff2bb4..543367ed 100644 --- a/shared/components/logos/logos.tsx +++ b/shared/components/logos/logos.tsx @@ -1,5 +1,4 @@ -import { LidoLogo } from '@lidofinance/lido-ui'; -import Link from 'next/link'; +import { LidoLogo, Link } from '@lidofinance/lido-ui'; import { FC } from 'react'; import { LogoLidoStyle } from './styles'; @@ -7,7 +6,7 @@ import { LogoLidoStyle } from './styles'; export const LogoLido: FC = () => ( - + ); diff --git a/shared/components/logos/styles.tsx b/shared/components/logos/styles.tsx index dbbd3672..293a0e74 100644 --- a/shared/components/logos/styles.tsx +++ b/shared/components/logos/styles.tsx @@ -16,4 +16,8 @@ export const LogoLidoStyle = styled.div` ${({ theme }) => theme.mediaQueries.lg} { align-self: start; } + + span { + display: block; + } `; diff --git a/shared/hooks/use-show-rule.ts b/shared/hooks/use-show-rule.ts index 2f259cce..19b70c5a 100644 --- a/shared/hooks/use-show-rule.ts +++ b/shared/hooks/use-show-rule.ts @@ -37,7 +37,7 @@ export const useShowRule = () => { case 'IS_CONNECTED_WALLET': return isConnectedWallet; case 'NOT_NODE_OPERATOR': - return !nodeOperator; + return isConnectedWallet && !nodeOperator; case 'IS_NODE_OPERATOR': return !!nodeOperator; case 'CAN_CREATE': diff --git a/shared/layout/footer/footer.tsx b/shared/layout/footer/footer.tsx index 3cf3811f..f4576f85 100644 --- a/shared/layout/footer/footer.tsx +++ b/shared/layout/footer/footer.tsx @@ -3,13 +3,7 @@ import { FC } from 'react'; import { getExternalLinks } from 'consts/external-links'; import { LogoLido, Stack } from 'shared/components'; -import { - FooterDivider, - FooterLink, - FooterStyle, - LinkDivider, - Version, -} from './styles'; +import { FooterLink, FooterStyle, LinkDivider, Version } from './styles'; const getVersionInfo = () => { const { version, branch } = buildInfo; @@ -44,7 +38,7 @@ const { feedbackForm } = getExternalLinks(); // TODO: matomo events export const Footer: FC = () => { return ( - + @@ -58,7 +52,6 @@ export const Footer: FC = () => { Feedback form {label} - ); }; diff --git a/shared/layout/footer/styles.tsx b/shared/layout/footer/styles.tsx index 1fa8b9c3..9ded074f 100644 --- a/shared/layout/footer/styles.tsx +++ b/shared/layout/footer/styles.tsx @@ -1,9 +1,10 @@ -import { Container, Link } from '@lidofinance/lido-ui'; +import { Link } from '@lidofinance/lido-ui'; import styled from 'styled-components'; import { NAV_MOBILE_MEDIA } from 'styles/constants'; -export const FooterStyle = styled(Container)` +export const FooterStyle = styled.footer` + grid-area: footer; position: relative; box-sizing: border-box; color: var(--lido-color-text); @@ -12,15 +13,28 @@ export const FooterStyle = styled(Container)` flex-wrap: wrap; column-gap: 32px; - width: 100%; - max-width: 1424px; - padding: 24px 32px; + align-self: center; ${NAV_MOBILE_MEDIA} { margin-bottom: 60px; padding: 20px 18px; justify-content: center; } + + &::before { + content: ''; + position: absolute; + top: -23px; + left: 0; + width: 100%; + height: 1px; + background: var(--lido-color-popupMenuItemBgActiveHover); + opacity: 0.12; + + ${NAV_MOBILE_MEDIA} { + display: none; + } + } `; export const FooterLink = styled(Link)` @@ -60,20 +74,6 @@ export const LinkDivider = styled.div` margin: 2px 16px; `; -export const FooterDivider = styled.div` - position: absolute; - top: 0; - left: 32px; - width: calc(100% - 64px); - height: 1px; - background: var(--lido-color-popupMenuItemBgActiveHover); - opacity: 0.12; - - ${NAV_MOBILE_MEDIA} { - display: none; - } -`; - export const Version = styled(FooterLink)` margin-left: auto; padding: 2px 5px; diff --git a/shared/layout/header/components/logos.tsx b/shared/layout/header/components/logos.tsx new file mode 100644 index 00000000..01ea3a01 --- /dev/null +++ b/shared/layout/header/components/logos.tsx @@ -0,0 +1,14 @@ +import { Link, Text } from '@lidofinance/lido-ui'; +import { FC } from 'react'; +import { LogoLido } from 'shared/components'; +import { LogoDivider, LogosStyle } from '../styles'; + +export const Logos: FC = () => ( + + + + + CSM + + +); diff --git a/shared/layout/header/dummy-header.tsx b/shared/layout/header/dummy-header.tsx index a5313c2f..3275ceca 100644 --- a/shared/layout/header/dummy-header.tsx +++ b/shared/layout/header/dummy-header.tsx @@ -1,26 +1,13 @@ -import { ReactComponent as HomeIcon } from 'assets/icons/home.svg'; -import Link from 'next/link'; import { FC } from 'react'; -import { LogoLido } from 'shared/components'; import HeaderTheme from './components/header-theme'; -import { Nav, NavLink } from './components/navigation/styles'; -import { HeaderActionsStyle, HeaderContentStyle, HeaderStyle } from './styles'; +import { Logos } from './components/logos'; +import { HeaderActionsStyle, HeaderStyle } from './styles'; export const DummyHeader: FC = () => ( - - - - - - - - + + + + + ); diff --git a/shared/layout/header/header.tsx b/shared/layout/header/header.tsx index 83b94b29..fae4d532 100644 --- a/shared/layout/header/header.tsx +++ b/shared/layout/header/header.tsx @@ -1,31 +1,25 @@ import { FC } from 'react'; -import { LogoLido } from 'shared/components'; import { config } from 'config'; -import { AlertContainer } from 'shared/alerts'; import HeaderChain from './components/header-chain'; import HeaderEaMember from './components/header-ea-member'; import HeaderNodeOperator from './components/header-node-operator'; import { HeaderSettingsButton } from './components/header-settings-button'; import HeaderTheme from './components/header-theme'; import HeaderWallet from './components/header-wallet'; -import { Navigation } from './components/navigation/navigation'; -import { HeaderActionsStyle, HeaderContentStyle, HeaderStyle } from './styles'; +import { Logos } from './components/logos'; +import { HeaderActionsStyle, HeaderStyle } from './styles'; export const Header: FC = () => ( - - - - - - - - - - {config.ipfsMode && } - - - - + + + + + + + + {config.ipfsMode && } + + ); diff --git a/shared/layout/header/styles.tsx b/shared/layout/header/styles.tsx index 466b591e..7fc82e2c 100644 --- a/shared/layout/header/styles.tsx +++ b/shared/layout/header/styles.tsx @@ -1,10 +1,14 @@ -import { Container, ContainerProps, ThemeToggler } from '@lidofinance/lido-ui'; +import { Divider, ThemeToggler } from '@lidofinance/lido-ui'; import { LogoLidoStyle } from 'shared/components/logos/styles'; import styled, { keyframes } from 'styled-components'; import { NAV_MOBILE_MEDIA } from 'styles/constants'; -export const HeaderContentStyle = styled.div` +export const HeaderStyle = styled.header` + grid-area: header; + position: relative; + align-self: center; + display: flex; align-items: center; @@ -20,14 +24,6 @@ export const HeaderContentStyle = styled.div` } `; -export const HeaderStyle = styled((props: ContainerProps) => ( - -))` - position: relative; - padding-top: 18px; - padding-bottom: 18px; -`; - export const HeaderActionsStyle = styled.div` position: relative; margin-left: auto; @@ -84,3 +80,13 @@ export const IPFSInfoBoxOnlyDesktopWrapper = styled.div` export const ThemeTogglerStyle = styled(ThemeToggler)` margin: 0; `; + +export const LogosStyle = styled.div` + display: flex; + gap: 12px; + align-items: center; +`; + +export const LogoDivider = styled(Divider).attrs({ type: 'vertical' })` + opacity: 0.6; +`; diff --git a/shared/layout/layout.tsx b/shared/layout/layout.tsx index d61b2d90..403fe551 100644 --- a/shared/layout/layout.tsx +++ b/shared/layout/layout.tsx @@ -1,23 +1,23 @@ import { FC, PropsWithChildren, ReactNode, useEffect } from 'react'; -import { ContainerProps } from '@lidofinance/lido-ui'; - +import { AlertContainer } from 'shared/alerts'; import { trackMatomoEvent, WithMatomoEvent } from 'utils'; import { Footer } from './footer/footer'; import { DummyHeader } from './header/dummy-header'; import { Header } from './header/header'; -import { Main } from './main/main'; +import { Navigation } from './navigation/navigation'; import { Content, Heading, + LayoutStyle, LayoutSubTitleStyle, LayoutTitleStyle, + Main, } from './styles'; type Props = { title?: ReactNode; subtitle?: ReactNode; - containerSize?: ContainerProps['size']; dummy?: boolean; }; @@ -26,7 +26,6 @@ export const Layout: FC>> = ({ dummy, title, subtitle, - containerSize, matomoEvent, }) => { const titlesCount = [title, subtitle].filter(Boolean).length; @@ -36,9 +35,18 @@ export const Layout: FC>> = ({ }, [matomoEvent]); return ( - <> - {dummy ? :
      } -
      + + {dummy ? ( + + ) : ( + <> +
      + + + + )} + +
      {title && {title}} {subtitle && {subtitle}} @@ -46,6 +54,6 @@ export const Layout: FC>> = ({ {children}
      - + ); }; diff --git a/shared/layout/main/main.tsx b/shared/layout/main/main.tsx deleted file mode 100644 index f668082a..00000000 --- a/shared/layout/main/main.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { FC } from 'react'; -import { ContainerProps } from '@lidofinance/lido-ui'; - -import { MainStyle } from './styles'; - -export const Main: FC = (props) => { - const { size = 'tight', ...rest } = props; - - return ; -}; diff --git a/shared/layout/main/styles.tsx b/shared/layout/main/styles.tsx deleted file mode 100644 index 6c311a98..00000000 --- a/shared/layout/main/styles.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { Container, ContainerProps } from '@lidofinance/lido-ui'; -import styled, { css } from 'styled-components'; - -export const MainStyle = styled(Container)` - position: relative; - margin-top: ${({ theme }) => theme.spaceMap.sm}px; - margin-bottom: ${({ theme }) => theme.spaceMap.xxl}px; - padding: 0; - - ${({ theme }) => theme.mediaQueries.lg} { - padding: 0 20px; - } - - ${({ size }) => - size === 'tight' && - css` - max-width: 590px; - `}; -`; diff --git a/shared/layout/header/components/navigation/navigation.tsx b/shared/layout/navigation/navigation.tsx similarity index 100% rename from shared/layout/header/components/navigation/navigation.tsx rename to shared/layout/navigation/navigation.tsx diff --git a/shared/layout/header/components/navigation/styles.tsx b/shared/layout/navigation/styles.tsx similarity index 94% rename from shared/layout/header/components/navigation/styles.tsx rename to shared/layout/navigation/styles.tsx index 7adab00c..68d0673d 100644 --- a/shared/layout/header/components/navigation/styles.tsx +++ b/shared/layout/navigation/styles.tsx @@ -4,7 +4,11 @@ import styled, { css } from 'styled-components'; import { NAV_MOBILE_HEIGHT, NAV_MOBILE_MEDIA } from 'styles/constants'; export const desktopCss = css` - margin: 0 46px; + grid-area: sidebar; + flex-direction: column; + align-items: start; + + margin-top: 4rem; display: flex; gap: 32px; @@ -29,13 +33,12 @@ const mobileCss = css` height: ${NAV_MOBILE_HEIGHT}px; `; -export const Nav = styled.div` +export const Nav = styled.nav` ${desktopCss} // mobile kicks in on a bit higher width for nav ${NAV_MOBILE_MEDIA} { ${mobileCss} } - align-items: center; z-index: 6; `; diff --git a/shared/layout/header/components/navigation/use-nav-items.tsx b/shared/layout/navigation/use-nav-items.tsx similarity index 100% rename from shared/layout/header/components/navigation/use-nav-items.tsx rename to shared/layout/navigation/use-nav-items.tsx diff --git a/shared/layout/styles.tsx b/shared/layout/styles.tsx index 51214695..6e4106c9 100644 --- a/shared/layout/styles.tsx +++ b/shared/layout/styles.tsx @@ -1,8 +1,19 @@ -import { H1 } from '@lidofinance/lido-ui'; +import { Container, H1 } from '@lidofinance/lido-ui'; import styled from 'styled-components'; import { NAV_MOBILE_MEDIA } from 'styles/constants'; +export const LayoutStyle = styled(Container)` + display: grid; + grid-template-columns: 180px auto 180px; + grid-template-rows: 80px auto 72px; + grid-gap: 1em; + grid-template-areas: + 'header header header' + 'sidebar main alerts' + 'footer footer footer'; +`; + export const Heading = styled.header<{ $titlesCount: number }>` display: flex; flex-direction: column; @@ -55,3 +66,19 @@ export const Content = styled.div` width: 100%; } `; + +export const Main = styled.main` + grid-area: main; + position: relative; + margin-inline: auto; + margin-top: ${({ theme }) => theme.spaceMap.sm}px; + margin-bottom: ${({ theme }) => theme.spaceMap.xxl}px; + padding: 0; + width: 100%; + + ${({ theme }) => theme.mediaQueries.lg} { + padding: 0 20px; + } + + max-width: 590px; +`; diff --git a/shared/navigate/switcher/styles.tsx b/shared/navigate/switcher/styles.tsx index f511d5c1..def9a8ee 100644 --- a/shared/navigate/switcher/styles.tsx +++ b/shared/navigate/switcher/styles.tsx @@ -2,8 +2,7 @@ import styled from 'styled-components'; import { LocalLink } from '../local-link'; export const SwitchWrapper = styled.div<{ $count: number }>` - width: 268px; - width: ${({ $count }) => `${$count * 134}px`}; + width: ${({ $count }) => `${$count * 134}px`} !important; height: 44px; background-color: var(--lido-color-backgroundDarken); border-radius: 22px; From fae12124f13e09aa7a27010a7cfcaefdbddbf6b6 Mon Sep 17 00:00:00 2001 From: exromany Date: Mon, 31 Mar 2025 11:04:57 +0300 Subject: [PATCH 062/418] feat: change layout --- features/view-keys/view-keys.tsx | 8 +-- shared/alerts/components/alert-container.tsx | 28 +++++---- shared/components/logos/styles.tsx | 4 +- shared/layout/footer/styles.tsx | 8 +-- shared/layout/header/styles.tsx | 16 ++--- shared/layout/navigation/styles.tsx | 25 +++++--- shared/layout/styles.tsx | 62 +++++++++++++++++--- styles/constants.ts | 4 +- styles/global.ts | 6 +- 9 files changed, 104 insertions(+), 57 deletions(-) diff --git a/features/view-keys/view-keys.tsx b/features/view-keys/view-keys.tsx index 8be44488..68dfc29f 100644 --- a/features/view-keys/view-keys.tsx +++ b/features/view-keys/view-keys.tsx @@ -1,7 +1,7 @@ import NoSSRWrapper from 'shared/components/no-ssr-wrapper'; import { useWeb3Key } from 'shared/hooks/useWeb3Key'; import { ViewKeysSection } from './view-keys-section'; -import { ExtraWidth, Faq } from 'shared/components'; +import { Faq } from 'shared/components'; import { DepositQueue } from './deposit-queue'; export const ViewKeys = () => { @@ -9,10 +9,8 @@ export const ViewKeys = () => { return ( <> - - - - + + diff --git a/shared/alerts/components/alert-container.tsx b/shared/alerts/components/alert-container.tsx index 3ac61dec..b13db2ff 100644 --- a/shared/alerts/components/alert-container.tsx +++ b/shared/alerts/components/alert-container.tsx @@ -4,26 +4,30 @@ import styled from 'styled-components'; import { useAlertActions } from '../alert-provider'; const AlertContainerStyled = styled.div` - position: absolute; - top: 100%; - right: 32px; - width: 300px; - display: flex; flex-direction: column; gap: ${({ theme }) => theme.spaceMap.md}px; - @media screen and (max-width: 1286px) { - position: static; - width: initial; - max-width: 590px; /* like Main */ - margin: 0 auto; - padding: 0 20px; - } + margin: 0 auto; :not(:empty) { margin-top: 20px; } + + grid-area: none; + position: absolute; + right: 32px; + top: 80px; + width: calc(50% - 350px); + max-width: 300px; + grid-area: none; + + ${({ theme }) => theme.mediaQueries.xl} { + grid-area: alerts; + position: static; + width: 100%; + max-width: var(--layout-main-width); + } `; export const AlertContainer: FC = () => { diff --git a/shared/components/logos/styles.tsx b/shared/components/logos/styles.tsx index 293a0e74..a39c069f 100644 --- a/shared/components/logos/styles.tsx +++ b/shared/components/logos/styles.tsx @@ -8,13 +8,13 @@ export const LogoLidoStyle = styled.div` flex-shrink: 0; cursor: pointer; - ${({ theme }) => theme.mediaQueries.md} { + ${({ theme }) => theme.mediaQueries.lg} { width: 14px; justify-content: flex-start; } ${({ theme }) => theme.mediaQueries.lg} { - align-self: start; + /* align-self: start; */ } span { diff --git a/shared/layout/footer/styles.tsx b/shared/layout/footer/styles.tsx index 9ded074f..00d7c000 100644 --- a/shared/layout/footer/styles.tsx +++ b/shared/layout/footer/styles.tsx @@ -13,13 +13,9 @@ export const FooterStyle = styled.footer` flex-wrap: wrap; column-gap: 32px; + justify-content: center; align-self: center; - - ${NAV_MOBILE_MEDIA} { - margin-bottom: 60px; - padding: 20px 18px; - justify-content: center; - } + margin-block: 24px; &::before { content: ''; diff --git a/shared/layout/header/styles.tsx b/shared/layout/header/styles.tsx index 7fc82e2c..cda2b6fa 100644 --- a/shared/layout/header/styles.tsx +++ b/shared/layout/header/styles.tsx @@ -1,5 +1,4 @@ import { Divider, ThemeToggler } from '@lidofinance/lido-ui'; -import { LogoLidoStyle } from 'shared/components/logos/styles'; import styled, { keyframes } from 'styled-components'; import { NAV_MOBILE_MEDIA } from 'styles/constants'; @@ -11,17 +10,10 @@ export const HeaderStyle = styled.header` display: flex; align-items: center; + flex-wrap: nowrap; - flex-wrap: wrap; row-gap: 8px; - - @media screen and (max-width: 880px) { - flex-wrap: nowrap; - } - - ${LogoLidoStyle} { - height: 44px; - } + margin-block: 18px; `; export const HeaderActionsStyle = styled.div` @@ -85,6 +77,10 @@ export const LogosStyle = styled.div` display: flex; gap: 12px; align-items: center; + + height: 28px; + margin-block: 8px; + align-self: start; `; export const LogoDivider = styled(Divider).attrs({ type: 'vertical' })` diff --git a/shared/layout/navigation/styles.tsx b/shared/layout/navigation/styles.tsx index 68d0673d..e2c2a586 100644 --- a/shared/layout/navigation/styles.tsx +++ b/shared/layout/navigation/styles.tsx @@ -1,10 +1,14 @@ import { CounterStyle } from 'shared/components/counter/styles'; import styled, { css } from 'styled-components'; -import { NAV_MOBILE_HEIGHT, NAV_MOBILE_MEDIA } from 'styles/constants'; +import { NAV_MOBILE_MEDIA } from 'styles/constants'; export const desktopCss = css` - grid-area: sidebar; + grid-area: nav; + position: sticky; + top: 3rem; + align-self: start; + flex-direction: column; align-items: start; @@ -18,19 +22,22 @@ export const desktopCss = css` `; const mobileCss = css` + display: grid; + grid-auto-columns: 1fr; + grid-auto-flow: column; + grid-gap: 16px; + margin: 0; position: fixed; + top: auto; bottom: 0; left: 0; right: 0; padding: 8px; + background-color: var(--lido-color-foreground); - display: flex; - gap: 32px; - justify-content: space-around; - align-items: center; border-top: 1px solid var(--lido-color-border); - height: ${NAV_MOBILE_HEIGHT}px; + min-height: 60px; `; export const Nav = styled.nav` @@ -39,6 +46,9 @@ export const Nav = styled.nav` ${NAV_MOBILE_MEDIA} { ${mobileCss} } + &:empty { + display: none; + } z-index: 6; `; @@ -83,6 +93,7 @@ export const NavLink = styled.span<{ $active?: boolean }>` ${NAV_MOBILE_MEDIA} { flex-direction: column; text-transform: none; + text-align: center; font-weight: 500; font-size: ${({ theme }) => theme.fontSizesMap.xxxs}px; line-height: 1.2em; diff --git a/shared/layout/styles.tsx b/shared/layout/styles.tsx index 6e4106c9..ac7c7300 100644 --- a/shared/layout/styles.tsx +++ b/shared/layout/styles.tsx @@ -4,14 +4,62 @@ import styled from 'styled-components'; import { NAV_MOBILE_MEDIA } from 'styles/constants'; export const LayoutStyle = styled(Container)` + --layout-main-width: 590px; + --layout-side-width: 170px; + + position: relative; + min-height: 100%; display: grid; - grid-template-columns: 180px auto 180px; - grid-template-rows: 80px auto 72px; - grid-gap: 1em; + grid-template-columns: var(--layout-side-width) 1fr var(--layout-side-width); + grid-template-rows: auto 1fr auto; + grid-column-gap: 14px; grid-template-areas: 'header header header' - 'sidebar main alerts' + 'nav main alerts' 'footer footer footer'; + + ${({ theme }) => theme.mediaQueries.xl} { + grid-template-columns: var(--layout-side-width) 1fr; + grid-template-rows: + auto auto 1fr + auto; + grid-template-areas: + 'header header' + 'nav alerts' + 'nav main' + 'footer footer'; + + &:has(nav:empty), + &:not(:has(nav)) { + grid-template-columns: 1fr; + grid-template-areas: + 'header' + 'alerts' + 'main' + 'footer'; + } + } + + ${NAV_MOBILE_MEDIA} { + grid-template-rows: auto auto 1fr auto 60px; + grid-template-columns: 1fr; + grid-template-areas: + 'header' + 'alerts' + 'main' + 'footer' + 'nav'; + + &:has(nav:empty), + &:not(:has(nav)) { + grid-template-rows: auto auto 1fr auto; + grid-template-areas: + 'header' + 'alerts' + 'main' + 'footer'; + } + } `; export const Heading = styled.header<{ $titlesCount: number }>` @@ -76,9 +124,5 @@ export const Main = styled.main` padding: 0; width: 100%; - ${({ theme }) => theme.mediaQueries.lg} { - padding: 0 20px; - } - - max-width: 590px; + max-width: var(--layout-main-width); `; diff --git a/styles/constants.ts b/styles/constants.ts index 6809ed3a..e198a29c 100644 --- a/styles/constants.ts +++ b/styles/constants.ts @@ -1,5 +1,3 @@ -export const NAV_MOBILE_HEIGHT = 60; - -export const NAV_MOBILE_MAX_WIDTH = 880; +export const NAV_MOBILE_MAX_WIDTH = 840; export const NAV_MOBILE_MEDIA = `@media screen and (max-width: ${NAV_MOBILE_MAX_WIDTH}px)`; diff --git a/styles/global.ts b/styles/global.ts index 6d7a8beb..46e38b11 100644 --- a/styles/global.ts +++ b/styles/global.ts @@ -16,6 +16,9 @@ const GlobalStyle = createGlobalStyle` html, body { width: 100%; + min-height: 100%; + display: grid; + grid-template-rows: 1fr; } body { background: var(--lido-color-background); @@ -27,9 +30,6 @@ const GlobalStyle = createGlobalStyle` font-weight: 500; text-size-adjust: none; } - main { - min-height: calc(100vh - 224px); - } a { cursor: pointer; text-decoration: none; From 5efe2c7b7be71d6933d8839b6582df4342087b55 Mon Sep 17 00:00:00 2001 From: exromany Date: Mon, 31 Mar 2025 11:54:37 +0300 Subject: [PATCH 063/418] fix: header wraping on big screen --- shared/layout/header/styles.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/shared/layout/header/styles.tsx b/shared/layout/header/styles.tsx index cda2b6fa..28c41b09 100644 --- a/shared/layout/header/styles.tsx +++ b/shared/layout/header/styles.tsx @@ -24,10 +24,8 @@ export const HeaderActionsStyle = styled.div` flex-shrink: 1; gap: ${({ theme }) => theme.spaceMap.sm}px; - ${({ theme }) => theme.mediaQueries.lg} { - flex-wrap: wrap; - justify-content: end; - } + flex-wrap: wrap; + justify-content: end; `; export const HeaderWalletChainWrapper = styled.div` From 6c74101e22e0d9d50c4fdd9644ce42f0bc5ba97e Mon Sep 17 00:00:00 2001 From: exromany Date: Thu, 3 Apr 2025 20:47:52 +0300 Subject: [PATCH 064/418] chore: view keys drop index column --- features/view-keys/view-keys-section/keys-table.tsx | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/features/view-keys/view-keys-section/keys-table.tsx b/features/view-keys/view-keys-section/keys-table.tsx index 49b8ec78..c285114d 100644 --- a/features/view-keys/view-keys-section/keys-table.tsx +++ b/features/view-keys/view-keys-section/keys-table.tsx @@ -1,4 +1,4 @@ -import { Tbody, Td, Text, Th, Thead, Tr } from '@lidofinance/lido-ui'; +import { Tbody, Td, Th, Thead, Tr } from '@lidofinance/lido-ui'; import { FC } from 'react'; import { Address, @@ -21,7 +21,6 @@ export const KeysTable: FC = ({ keys }) => { - # Key Status Comment @@ -30,11 +29,6 @@ export const KeysTable: FC = ({ keys }) => { {sortedKeys?.map(({ key, index, statuses }) => ( - - - {index + 1} - -
      Date: Thu, 3 Apr 2025 21:12:29 +0300 Subject: [PATCH 065/418] chore: monitoring page with external dashboards --- assets/icons/meter.svg | 3 +++ consts/matomo-click-events.ts | 6 ++++++ consts/urls.ts | 1 + features/dashboard/dashboard.tsx | 2 -- .../external/external-button-link.tsx | 0 .../external/external-section.tsx | 14 +++----------- .../{dashboard => monitoring}/external/index.ts | 0 .../{dashboard => monitoring}/external/styles.ts | 0 .../external/use-external-button.tsx | 0 features/monitoring/index.ts | 1 + features/monitoring/monitoring-page.tsx | 16 ++++++++++++++++ features/monitoring/monitoring.tsx | 10 ++++++++++ pages/monitoring.tsx | 16 ++++++++++++++++ shared/layout/navigation/styles.tsx | 2 +- shared/layout/navigation/use-nav-items.tsx | 8 ++++++++ 15 files changed, 65 insertions(+), 14 deletions(-) create mode 100644 assets/icons/meter.svg rename features/{dashboard => monitoring}/external/external-button-link.tsx (100%) rename features/{dashboard => monitoring}/external/external-section.tsx (60%) rename features/{dashboard => monitoring}/external/index.ts (100%) rename features/{dashboard => monitoring}/external/styles.ts (100%) rename features/{dashboard => monitoring}/external/use-external-button.tsx (100%) create mode 100644 features/monitoring/index.ts create mode 100644 features/monitoring/monitoring-page.tsx create mode 100644 features/monitoring/monitoring.tsx create mode 100644 pages/monitoring.tsx diff --git a/assets/icons/meter.svg b/assets/icons/meter.svg new file mode 100644 index 00000000..52f79c4c --- /dev/null +++ b/assets/icons/meter.svg @@ -0,0 +1,3 @@ + + + diff --git a/consts/matomo-click-events.ts b/consts/matomo-click-events.ts index a2527e88..0524f832 100644 --- a/consts/matomo-click-events.ts +++ b/consts/matomo-click-events.ts @@ -78,6 +78,7 @@ export const enum MATOMO_CLICK_EVENTS_TYPES { pageAddKeys = 'pageAddKeys', pageViewKeys = 'pageViewKeys', pageRemoveKeys = 'pageRemoveKeys', + pageMonitoring = 'pageMonitoring', pageAddBond = 'pageAddBond', pageClaimBond = 'pageClaimBond', pageUnlockBond = 'pageUnlockBond', @@ -416,6 +417,11 @@ export const MATOMO_CLICK_EVENTS: Record< 'View page «RemoveKeys»', prefixed`view_remove_keys_page`, ], + [MATOMO_CLICK_EVENTS_TYPES.pageMonitoring]: [ + MATOMO_APP_NAME, + 'View page «Monitoring»', + prefixed`view_monitoring_page`, + ], [MATOMO_CLICK_EVENTS_TYPES.pageAddBond]: [ MATOMO_APP_NAME, 'View page «AddBond»', diff --git a/consts/urls.ts b/consts/urls.ts index b738260f..2db58895 100644 --- a/consts/urls.ts +++ b/consts/urls.ts @@ -9,6 +9,7 @@ export const PATH = { KEYS_REMOVE: '/keys/remove', KEYS_VIEW: '/keys/view', KEYS_NORMALIZE: '/keys/normalize', + MONITORING: '/monitoring', BOND: '/bond', BOND_CLAIM: '/bond/claim', BOND_ADD: '/bond/add', diff --git a/features/dashboard/dashboard.tsx b/features/dashboard/dashboard.tsx index 13dc26b1..7ff6d93d 100644 --- a/features/dashboard/dashboard.tsx +++ b/features/dashboard/dashboard.tsx @@ -2,7 +2,6 @@ import { FC } from 'react'; import { BondSection } from './bond'; import { KeysSection } from './keys'; import { RolesSection } from './roles'; -import { ExternalSection } from './external'; import { SurveysCta } from './surveys-cta'; export const Dashboard: FC = () => { @@ -12,7 +11,6 @@ export const Dashboard: FC = () => { - ); }; diff --git a/features/dashboard/external/external-button-link.tsx b/features/monitoring/external/external-button-link.tsx similarity index 100% rename from features/dashboard/external/external-button-link.tsx rename to features/monitoring/external/external-button-link.tsx diff --git a/features/dashboard/external/external-section.tsx b/features/monitoring/external/external-section.tsx similarity index 60% rename from features/dashboard/external/external-section.tsx rename to features/monitoring/external/external-section.tsx index dd4e8fc9..7462a3c1 100644 --- a/features/dashboard/external/external-section.tsx +++ b/features/monitoring/external/external-section.tsx @@ -1,6 +1,5 @@ -import { Accordion, Text } from '@lidofinance/lido-ui'; import { FC } from 'react'; -import { Stack } from 'shared/components'; +import { SectionBlock, Stack } from 'shared/components'; import { ExternalButtonLink } from './external-button-link'; import { useExternalButtons } from './use-external-button'; @@ -10,19 +9,12 @@ export const ExternalSection: FC = () => { if (buttons.length === 0) return null; return ( - - External dashboards - - } - > + {buttons.map((button) => ( ))} - + ); }; diff --git a/features/dashboard/external/index.ts b/features/monitoring/external/index.ts similarity index 100% rename from features/dashboard/external/index.ts rename to features/monitoring/external/index.ts diff --git a/features/dashboard/external/styles.ts b/features/monitoring/external/styles.ts similarity index 100% rename from features/dashboard/external/styles.ts rename to features/monitoring/external/styles.ts diff --git a/features/dashboard/external/use-external-button.tsx b/features/monitoring/external/use-external-button.tsx similarity index 100% rename from features/dashboard/external/use-external-button.tsx rename to features/monitoring/external/use-external-button.tsx diff --git a/features/monitoring/index.ts b/features/monitoring/index.ts new file mode 100644 index 00000000..424b1359 --- /dev/null +++ b/features/monitoring/index.ts @@ -0,0 +1 @@ +export * from './monitoring-page'; diff --git a/features/monitoring/monitoring-page.tsx b/features/monitoring/monitoring-page.tsx new file mode 100644 index 00000000..8fb896a2 --- /dev/null +++ b/features/monitoring/monitoring-page.tsx @@ -0,0 +1,16 @@ +import { FC } from 'react'; +import { Layout } from 'shared/layout'; +import { Monitoring } from './monitoring'; +import { MATOMO_CLICK_EVENTS_TYPES } from 'consts/matomo-click-events'; + +export const MonitoringPage: FC = () => { + return ( + + + + ); +}; diff --git a/features/monitoring/monitoring.tsx b/features/monitoring/monitoring.tsx new file mode 100644 index 00000000..3f809169 --- /dev/null +++ b/features/monitoring/monitoring.tsx @@ -0,0 +1,10 @@ +import { FC } from 'react'; +import { ExternalSection } from './external'; + +export const Monitoring: FC = () => { + return ( + <> + + + ); +}; diff --git a/pages/monitoring.tsx b/pages/monitoring.tsx new file mode 100644 index 00000000..23344b55 --- /dev/null +++ b/pages/monitoring.tsx @@ -0,0 +1,16 @@ +import { PATH } from 'consts/urls'; +import { MonitoringPage } from 'features/monitoring'; +import { getProps } from 'lib/getProps'; +import { Gate, GateLoaded, Navigate } from 'shared/navigate'; + +const Page = () => ( + + }> + + + +); + +export default Page; + +export const getServerSideProps = getProps(); diff --git a/shared/layout/navigation/styles.tsx b/shared/layout/navigation/styles.tsx index e2c2a586..ca3adbce 100644 --- a/shared/layout/navigation/styles.tsx +++ b/shared/layout/navigation/styles.tsx @@ -86,7 +86,7 @@ export const NavLink = styled.span<{ $active?: boolean }>` } svg { - fill: ${({ $active }) => + color: ${({ $active }) => $active ? `var(--lido-color-primary)` : `var(--lido-color-secondary)`}; } diff --git a/shared/layout/navigation/use-nav-items.tsx b/shared/layout/navigation/use-nav-items.tsx index 2d5d8549..f6195e4a 100644 --- a/shared/layout/navigation/use-nav-items.tsx +++ b/shared/layout/navigation/use-nav-items.tsx @@ -8,6 +8,7 @@ import { ReactComponent as HomeIcon } from 'assets/icons/home.svg'; import { ReactComponent as KeyIcon } from 'assets/icons/key.svg'; import { ReactComponent as WalletIcon } from 'assets/icons/wallet.svg'; import { ReactComponent as FileIcon } from 'assets/icons/file.svg'; +import { ReactComponent as MeterIcon } from 'assets/icons/meter.svg'; import { CounterInvalidKeys, CounterInvites, @@ -46,6 +47,13 @@ const routes: Route[] = [ showRules: ['IS_NODE_OPERATOR', 'CAN_CREATE'], suffix: , }, + { + name: 'Monitoring', + path: PATH.MONITORING, + icon: , + showRules: ['IS_NODE_OPERATOR'], + // TODO: suffix for bad attestation rate + }, { name: 'Bond & Rewards', path: PATH.BOND, From 95d949d9d548ae095f29ea3355e102a10b7f71bd Mon Sep 17 00:00:00 2001 From: Aleksandr Date: Fri, 4 Apr 2025 21:28:49 +0300 Subject: [PATCH 066/418] feat: setup e2e tests --- .eslintignore | 2 + .github/workflows/tests.yml | 117 ++ .gitignore | 1 + .prettierignore | 2 + package.json | 14 +- playwright.config.ts | 44 + tests/config/configFactory.ts | 35 + tests/config/configs/base.config.ts | 40 + tests/config/configs/prod.config.ts | 19 + tests/config/configs/staging.config.ts | 19 + tests/config/configs/testnet.config.ts | 18 + tests/config/index.ts | 10 + tests/config/report.config.ts | 88 ++ .../common/element.connectWalletModal.ts | 35 + .../elements/common/element.termAndPrivacy.ts | 24 + tests/pages/elements/controller.ts | 18 + tests/pages/header.page.ts | 30 + tests/pages/widget.service.ts | 50 + tests/widget/main.spec.ts | 8 + tests/widget/test.fixture.ts | 97 ++ yarn.lock | 1345 ++++++++++++++++- 21 files changed, 1977 insertions(+), 39 deletions(-) create mode 100644 .github/workflows/tests.yml create mode 100644 playwright.config.ts create mode 100644 tests/config/configFactory.ts create mode 100644 tests/config/configs/base.config.ts create mode 100644 tests/config/configs/prod.config.ts create mode 100644 tests/config/configs/staging.config.ts create mode 100644 tests/config/configs/testnet.config.ts create mode 100644 tests/config/index.ts create mode 100644 tests/config/report.config.ts create mode 100644 tests/pages/elements/common/element.connectWalletModal.ts create mode 100644 tests/pages/elements/common/element.termAndPrivacy.ts create mode 100644 tests/pages/elements/controller.ts create mode 100644 tests/pages/header.page.ts create mode 100644 tests/pages/widget.service.ts create mode 100644 tests/widget/main.spec.ts create mode 100644 tests/widget/test.fixture.ts diff --git a/.eslintignore b/.eslintignore index 55d8dacb..c299f7d9 100644 --- a/.eslintignore +++ b/.eslintignore @@ -15,3 +15,5 @@ # generated /generated + +**/.browser_context** diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 00000000..df8f71be --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,117 @@ +name: Test CSM-widget +run-name: CSM-widget. Env [${{ github.event.inputs.stand_type || 'testnet' }}] + +on: + workflow_dispatch: + inputs: + stand_type: + description: 'Stand type' + required: true + type: choice + default: testnet + options: + - prod + - staging + - testnet + - preview + tags: + description: 'Tags for running test suite' + required: false + type: choice + default: '-' + options: + - '-' + - smoke + preview_stand_url: + description: 'Preview stand url (only if "Stand type" is "preview")' + required: false + type: string + preview_stand_env: + description: 'Preview stand env (only if "Stand type" is "preview")' + required: false + type: choice + default: testnet + options: + - testnet + - infra + + pull_request: + paths: + - 'tests/widget/**' + +jobs: + test: + runs-on: ubuntu-22.04 + env: + # Required envs + RPC_URL_TOKEN: ${{ secrets.RPC_URL_TOKEN }} + WALLET_SECRET_PHRASE: ${{ secrets.WALLET_SECRET_PHRASE }} + WALLET_PASSWORD: ${{ secrets.WALLET_PASSWORD }} + STAND_TYPE: ${{ github.event.inputs.STAND_TYPE || 'testnet' }} + + # Common envs + QASE_API_TOKEN: ${{ secrets.QASE_API_TOKEN }} + QASE_PROJECT_ID: 'CSM' + GH_ACTION_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_BRANCH_REF_NAME: ${{ github.ref_name }} + GH_EVENT_NAME: ${{ github.event_name }} + NODE_OPTIONS: --max-old-space-size=4096 + TEST_TAGS: ${{ github.event.inputs.tags }} + TEST_BRANCH: ${{ github.event.inputs.branch }} + + # Optional envs + REFUSE_CF_BLOCK_NAME: ${{ secrets.REFUSE_CF_BLOCK_NAME }} + REFUSE_CF_BLOCK_VALUE: ${{ secrets.REFUSE_CF_BLOCK_VALUE }} + PUSHGATEWAY_URL: ${{ secrets.PUSHGATEWAY_URL }} + PUSHGATEWAY_USERNAME: ${{ secrets.PUSHGATEWAY_USERNAME }} + PUSHGATEWAY_PASSWORD: ${{ secrets.PUSHGATEWAY_PASSWORD }} + + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha }} + + - uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Install dependencies + run: yarn install --immutable + + - name: Install Playwright Browsers + run: yarn playwright install chromium --with-deps + + - name: Set up preview-stand credentials + if: ${{ github.event.inputs.stand_type == 'preview' }} + run: | + if [ -n "${{ github.event.inputs.preview_stand_url }}" ]; then + echo "Setting login and password for preview stand" + echo "PREVIEW_STAND_LOGIN=${{ secrets.PREVIEW_STAND_LOGIN }}" >> $GITHUB_ENV + echo "PREVIEW_STAND_PASSWORD=${{ secrets.PREVIEW_STAND_PASSWORD }}" >> $GITHUB_ENV + echo "PREVIEW_STAND_URL=${{ github.event.inputs.preview_stand_url }}" >> $GITHUB_ENV + echo "PREVIEW_STAND_ENV=${{ github.event.inputs.preview_stand_env }}" >> $GITHUB_ENV + else + echo "PREVIEW_STAND_URL not provided for preview environment" + exit 1 + fi + + - name: Run tests based on suite input + run: | + echo -e "\033[34m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\033[0m" + echo -e "🚀 Running on Stand Type: \033[1;34m$STAND_TYPE\033[0m" + echo -e "🧪 Test Tags: \033[1;34m$TEST_TAGS\033[0m" + echo -e "🌱 Branch: \033[1;34m${GITHUB_HEAD_REF:-$GITHUB_REF_NAME}\033[0m" + echo -e "\033[34m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\033[0m" + + xvfb-run --auto-servernum -- yarn test + + # - name: Fix test results when job is cancelled + # if: cancelled() + # run: yarn metrics:fix + + - uses: actions/upload-artifact@v4 + if: always() + with: + name: playwright-report + path: playwright-report/ + retention-days: 5 diff --git a/.gitignore b/.gitignore index 0a71a893..d2c3bb49 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,4 @@ yarn-error.log* .idea /public/runtime/ +**/.browser_context** diff --git a/.prettierignore b/.prettierignore index b87ad429..cec00323 100644 --- a/.prettierignore +++ b/.prettierignore @@ -13,3 +13,5 @@ /build CHANGELOG.md + +/.browser_context** diff --git a/package.json b/package.json index 95877de4..93d8d973 100644 --- a/package.json +++ b/package.json @@ -85,10 +85,14 @@ "@commitlint/cli": "^17.4.4", "@commitlint/config-conventional": "^17.4.4", "@commitlint/prompt": "^17.4.4", + "@lidofinance/browser-service": "1.0.0-alpha-move-br.3", "@lidofinance/eslint-config": "^0.43.0", + "@lidofinance/wallets-testing-extensions": "1.1.1-alpha-move-br.1", + "@lidofinance/wallets-testing-wallets": "1.30.0-alpha-move-br.1", + "@nestjs/common": "^11.0.12", "@next/bundle-analyzer": "^13.2.4", "@next/eslint-plugin-next": "^13.4.13", - "@playwright/test": "^1.29.2", + "@playwright/test": "^1.51.1", "@svgr/webpack": "8.1.0", "@typechain/ethers-v5": "^11.1.2", "@types/jest": "28.1.6", @@ -106,6 +110,7 @@ "@types/winston": "^2.4.4", "@typescript-eslint/eslint-plugin": "^6.2.1", "@typescript-eslint/parser": "^6.2.1", + "dotenv": "^16.4.7", "eslint": "^8.46.0", "eslint-config-prettier": "^9.0.0", "eslint-import-resolver-typescript": "^3.5.5", @@ -122,13 +127,16 @@ "jest": "^29.5.0", "jsonschema": "^1.4.1", "lint-staged": "^13.2.3", - "playwright": "^1.29.2", + "playwright-qase-reporter": "^2.0.17", "prettier": "^3.3.3", + "qase-javascript-commons": "^2.2.19", + "qaseio": "^2.4.3", "ts-jest": "^29.1.0", "typechain": "^8.1.1", "typescript": "^5.4.5", "url-loader": "^4.1.1", - "webpack-preprocessor-loader": "^1.3.0" + "webpack-preprocessor-loader": "^1.3.0", + "zod": "^3.24.2" }, "resolutions": { "postcss": "^8.4.31", diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 00000000..0ddf0b2e --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,44 @@ +import type { PlaywrightTestConfig } from '@playwright/test'; +import { widgetFullConfig } from 'tests/config'; +import { getReportConfig } from 'tests/config/report.config'; + +// TODO: move it pls +export const httpCredentials = + process.env.PREVIEW_STAND_LOGIN && process.env.PREVIEW_STAND_PASSWORD + ? { + username: process.env.PREVIEW_STAND_LOGIN, + password: process.env.PREVIEW_STAND_PASSWORD, + } + : undefined; + +const config: PlaywrightTestConfig = { + testDir: './tests', + timeout: 180 * 1000, + expect: { + timeout: 5000, + }, + fullyParallel: false, + forbidOnly: !!process.env.CI, + workers: 1, + reporter: getReportConfig(), + use: { + headless: false, + actionTimeout: 15000, + screenshot: { fullPage: true, mode: 'only-on-failure' }, + baseURL: widgetFullConfig.standConfig.standUrl, + trace: 'retain-on-failure', + permissions: ['clipboard-read'], + contextOptions: { + reducedMotion: 'reduce', + }, + httpCredentials: httpCredentials, + }, + projects: [ + { + name: 'widget', + testDir: './tests', + }, + ], +}; + +export default config; diff --git a/tests/config/configFactory.ts b/tests/config/configFactory.ts new file mode 100644 index 00000000..40fd82a2 --- /dev/null +++ b/tests/config/configFactory.ts @@ -0,0 +1,35 @@ +import { IConfig } from './configs/base.config'; +import { ProdConfig } from './configs/prod.config'; +import { StagingConfig } from './configs/staging.config'; +import { TestnetConfig } from './configs/testnet.config'; + +export class ConfigFactory { + private static instance: IConfig | null = null; + + // TODO: make it required arg + public static createConfig(standType?: string): IConfig { + if (ConfigFactory.instance) { + return ConfigFactory.instance; + } + + let config: IConfig; + + switch (standType) { + case 'prod': + config = new ProdConfig(); + break; + case 'staging': + config = new StagingConfig(); + break; + case 'testnet': + config = new TestnetConfig(); + break; + default: + throw new Error(`Unknown stand type: ${standType}`); + } + + // ConfigSchema.parse(config); + ConfigFactory.instance = config; + return config; + } +} diff --git a/tests/config/configs/base.config.ts b/tests/config/configs/base.config.ts new file mode 100644 index 00000000..4733c19b --- /dev/null +++ b/tests/config/configs/base.config.ts @@ -0,0 +1,40 @@ +import { + METAMASK_COMMON_CONFIG, + NetworkConfig, + WalletConfig, +} from '@lidofinance/wallets-testing-wallets'; +import { z } from 'zod'; + +export type StandConfig = { + standType: string; + standUrl: string; + networkConfig: NetworkConfig; +}; + +export interface IConfig { + standConfig: StandConfig; + walletConfig: WalletConfig; + getFullInfo(): string; +} + +export const ConfigSchema = z.object({ + standType: z.string(), + standUrl: z.string().url(), +}); + +export class BaseConfig implements IConfig { + public standConfig!: StandConfig; + public walletConfig: WalletConfig; + + constructor() { + this.walletConfig = { + SECRET_PHRASE: process.env.WALLET_SECRET_PHRASE || '', + PASSWORD: process.env.WALLET_PASSWORD || '', + COMMON: METAMASK_COMMON_CONFIG, + }; + } + + getFullInfo(): string { + throw new Error('Method not implemented.'); + } +} diff --git a/tests/config/configs/prod.config.ts b/tests/config/configs/prod.config.ts new file mode 100644 index 00000000..b84ff0ee --- /dev/null +++ b/tests/config/configs/prod.config.ts @@ -0,0 +1,19 @@ +import { BaseConfig } from './base.config'; + +export class ProdConfig extends BaseConfig { + constructor() { + super(); + + this.standConfig = { + standType: 'prod', + standUrl: 'https://csm.lido.fi', + networkConfig: { + chainId: 1, + tokenSymbol: 'ETH', + chainName: 'Ethereum Mainnet', + rpcUrl: `https://lb.drpc.org/ogrpc?network=ethereum&dkey=${process.env.RPC_URL_TOKEN}`, + scan: 'https://etherscan.io/', + }, + }; + } +} diff --git a/tests/config/configs/staging.config.ts b/tests/config/configs/staging.config.ts new file mode 100644 index 00000000..bb5a4f14 --- /dev/null +++ b/tests/config/configs/staging.config.ts @@ -0,0 +1,19 @@ +// StagingConfig.ts +import { BaseConfig } from './base.config'; + +export class StagingConfig extends BaseConfig { + constructor() { + super(); + this.standConfig = { + standType: 'staging', + standUrl: 'https://csm.infra-staging.org', + networkConfig: { + chainId: 1, + tokenSymbol: 'ETH', + chainName: 'Ethereum Mainnet', + rpcUrl: `https://lb.drpc.org/ogrpc?network=ethereum&dkey=${process.env.RPC_URL_TOKEN}`, + scan: 'https://etherscan.io/', + }, + }; + } +} diff --git a/tests/config/configs/testnet.config.ts b/tests/config/configs/testnet.config.ts new file mode 100644 index 00000000..42d771bb --- /dev/null +++ b/tests/config/configs/testnet.config.ts @@ -0,0 +1,18 @@ +import { BaseConfig } from './base.config'; + +export class TestnetConfig extends BaseConfig { + constructor() { + super(); + this.standConfig = { + standType: 'testnet', + standUrl: 'https://csm-hoodi.vercel.app/create', + networkConfig: { + chainId: 560048, + tokenSymbol: 'ETH', + chainName: 'Hoodi', + rpcUrl: `https://lb.drpc.org/ogrpc?network=hoodi&dkey=${process.env.RPC_URL_TOKEN}`, + scan: 'https://hoodi.etherscan.io/', + }, + }; + } +} diff --git a/tests/config/index.ts b/tests/config/index.ts new file mode 100644 index 00000000..38c9f8ba --- /dev/null +++ b/tests/config/index.ts @@ -0,0 +1,10 @@ +export * from './configFactory'; +import { config as envConfig } from 'dotenv'; +envConfig(); + +import { IConfig } from 'tests/config/configs/base.config'; +import { ConfigFactory } from 'tests/config'; + +export const widgetFullConfig: IConfig = ConfigFactory.createConfig( + process.env.STAND_TYPE, +); diff --git a/tests/config/report.config.ts b/tests/config/report.config.ts new file mode 100644 index 00000000..ce4bd948 --- /dev/null +++ b/tests/config/report.config.ts @@ -0,0 +1,88 @@ +import { ReporterDescription } from '@playwright/test'; +import { widgetFullConfig } from '.'; + +export const getReportConfig: () => ReporterDescription[] = function () { + const reporterConfig: ReporterDescription[] = [ + reporters.htmlReporter, + reporters.consoleReporter, + ]; + if (process.env.CI) { + reporterConfig.push( + reporters.githubReporter, + reporters.qaseReporter, + reporters.discordReport, + ); + } + return reporterConfig; +}; + +export const getTestRunName = () => { + return ( + `${ + process.env.GH_EVENT_NAME === 'schedule' ? 'Schedule Run' : 'Auto Run' + } ` + + `[s:@${process.env.TEST_SUITE || 'ALL'}] ` + + `[t:${process.env.TEST_TAGS || '-'}] ` + + `[b:${getBranchName()}]` + + `[w:${process.env.WALLET_NAME || 'metamask'}]` + ); +}; + +export const getBranchName = () => { + const branchName = + process.env.GITHUB_HEAD_REF || + process.env.TEST_BRANCH || + process.env.GH_BRANCH_REF_NAME || + 'none'; + + return branchName.replace('/', '-'); +}; + +export const getTestRunDescription = () => { + return ( + `Github run link: ${process.env.GH_ACTION_URL}\n` + + `Stand url: ${widgetFullConfig.standConfig.standUrl}\n` + + `Env: ${process.env.STAND_TYPE}` + ); +}; + +const reporters: { + htmlReporter: ReporterDescription; + consoleReporter: ReporterDescription; + githubReporter: ReporterDescription; + discordReport: ReporterDescription; + qaseReporter: ReporterDescription; +} = { + htmlReporter: ['html', { open: 'never' }], + consoleReporter: ['list', { printSteps: !process.env.CI }], + githubReporter: ['github'], + discordReport: [ + '@lidofinance/discord-reporter', + { + enabled: process.env.DISCORD_REPORT_ENABLED, + }, + ], + qaseReporter: [ + 'playwright-qase-reporter', + { + debug: false, + environment: process.env.STAND_TYPE, + mode: 'testops', // value 'testops' enables Qase reporter, 'off' disables + testops: { + api: { + token: process.env.QASE_API_TOKEN, + }, + project: process.env.QASE_PROJECT_ID, + uploadAttachments: true, + run: { + complete: true, + title: getTestRunName(), + description: getTestRunDescription(), + }, + batch: { + size: 10, + }, + }, + }, + ], +}; diff --git a/tests/pages/elements/common/element.connectWalletModal.ts b/tests/pages/elements/common/element.connectWalletModal.ts new file mode 100644 index 00000000..58aaf90c --- /dev/null +++ b/tests/pages/elements/common/element.connectWalletModal.ts @@ -0,0 +1,35 @@ +import { Locator, Page } from '@playwright/test'; + +export class ConnectWalletModal { + page: Page; + modal: Locator; + termOfUseAndPrivacyCheckBox: Locator; + moreWallets: Locator; + lessWallets: Locator; + + constructor(page: Page) { + this.page = page; + this.modal = this.page.locator('div[role="dialog"]', { + hasText: 'Connect wallet', + }); + this.termOfUseAndPrivacyCheckBox = this.modal.locator( + 'input[type=checkbox]', + ); + this.moreWallets = this.modal.getByText('More wallets'); + this.lessWallets = this.modal.getByText('Less wallets'); + } + + async isTermOfUseAndPrivacyChecked() { + return this.termOfUseAndPrivacyCheckBox.isChecked(); + } + + getWalletInModal(walletName: string) { + return this.modal + .getByRole('button') + .getByText(walletName, { exact: true }); + } + + async closePopUp() { + await this.modal.locator('button').nth(0).click(); + } +} diff --git a/tests/pages/elements/common/element.termAndPrivacy.ts b/tests/pages/elements/common/element.termAndPrivacy.ts new file mode 100644 index 00000000..b8df6929 --- /dev/null +++ b/tests/pages/elements/common/element.termAndPrivacy.ts @@ -0,0 +1,24 @@ +import { Locator, Page } from '@playwright/test'; + +export class TermAndPrivacy { + page: Page; + termAndPrivacy: Locator; + + constructor(page: Page) { + this.page = page; + this.termAndPrivacy = this.page + .locator('div[role="dialog"]') + .getByText('I certify that I have read and accept the updated'); + } + + async isTermOfUseAndPrivacyChecked() { + return this.page + .locator('div[role="dialog"]') + .locator('input[type=checkbox]') + .isChecked(); + } + + async confirmConditionWalletModal() { + await this.termAndPrivacy.check(); + } +} diff --git a/tests/pages/elements/controller.ts b/tests/pages/elements/controller.ts new file mode 100644 index 00000000..28ec05ea --- /dev/null +++ b/tests/pages/elements/controller.ts @@ -0,0 +1,18 @@ +import { Page } from '@playwright/test'; +import { Header } from '../header.page'; +import { TermAndPrivacy } from './common/element.termAndPrivacy'; +import { ConnectWalletModal } from './common/element.connectWalletModal'; + +export class ElementController { + page: Page; + header: Header; + connectWalletModal: ConnectWalletModal; + termAndPrivacy: TermAndPrivacy; + + constructor(page: Page) { + this.page = page; + this.header = new Header(this.page); + this.connectWalletModal = new ConnectWalletModal(this.page); + this.termAndPrivacy = new TermAndPrivacy(this.page); + } +} diff --git a/tests/pages/header.page.ts b/tests/pages/header.page.ts new file mode 100644 index 00000000..f14f729f --- /dev/null +++ b/tests/pages/header.page.ts @@ -0,0 +1,30 @@ +import { Locator, Page } from '@playwright/test'; + +export class Header { + page: Page; + header: Locator; + accountSection: Locator; + connectWalletBtn: Locator; + + constructor(page: Page) { + this.page = page; + this.header = this.page.locator('header'); + this.accountSection = this.header.getByTestId('accountSectionHeader'); + + this.connectWalletBtn = this.header.getByText('Connect').first(); + } + + async isAccountSectionVisible() { + await this.accountSection + .waitFor({ + state: 'visible', + timeout: 5000, + }) + .catch(() => { + console.error( + 'isAccountSectionVisible: Account section is not visible', + ); + }); + return this.accountSection.isVisible(); + } +} diff --git a/tests/pages/widget.service.ts b/tests/pages/widget.service.ts new file mode 100644 index 00000000..e8cb713d --- /dev/null +++ b/tests/pages/widget.service.ts @@ -0,0 +1,50 @@ +import { expect, Page, test } from '@playwright/test'; +import { ElementController } from './elements/controller'; +import { WalletPage, WalletTypes } from '@lidofinance/wallets-testing-wallets'; + +export class WidgetService { + constructor( + public page: Page, + public walletPage: WalletPage, + ) {} + + async connectWallet(expectConnectionState = true) { + await test.step('Connect wallet to widget', async () => { + const element = new ElementController(this.page); + if (await this.isConnectedWallet()) return; + await element.header.connectWalletBtn.click(); + await element.termAndPrivacy.confirmConditionWalletModal(); + const walletIcon = element.connectWalletModal.getWalletInModal( + this.walletPage.config.COMMON.CONNECT_BUTTON_NAME, + ); + if ( + (await walletIcon.isEnabled({ timeout: 500 })) && + this.walletPage.config.COMMON.WALLET_TYPE === WalletTypes.EOA + ) { + try { + const [connectWalletPage] = await Promise.all([ + this.page.context().waitForEvent('page', { timeout: 90000 }), + // @Fixme dbclick() when https://linear.app/lidofi/issue/SI-1447/mm-incorrect-network-required-double-click resolved + await walletIcon.dblclick(), + ]); + await this.walletPage.connectWallet(connectWalletPage); + } catch { + console.error('Wallet page didnt open'); + } + } + + expect( + await this.isConnectedWallet(), + expectConnectionState + ? 'Wallet should be connected' + : 'Wallet should be disconnected', + ).toBe(expectConnectionState); + }); + } + + async isConnectedWallet() { + return test.step('Check wallet connection', async () => { + return new ElementController(this.page).header.isAccountSectionVisible(); + }); + } +} diff --git a/tests/widget/main.spec.ts b/tests/widget/main.spec.ts new file mode 100644 index 00000000..931d2df9 --- /dev/null +++ b/tests/widget/main.spec.ts @@ -0,0 +1,8 @@ +import { test } from './test.fixture'; + +test.describe('First test suite', async () => { + test('First test case', async ({ widgetService }) => { + await widgetService.page.goto('/'); + await widgetService.connectWallet(); + }); +}); diff --git a/tests/widget/test.fixture.ts b/tests/widget/test.fixture.ts new file mode 100644 index 00000000..95e01c90 --- /dev/null +++ b/tests/widget/test.fixture.ts @@ -0,0 +1,97 @@ +import { BrowserService } from '@lidofinance/browser-service'; +import { test as base } from '@playwright/test'; +import { widgetFullConfig } from 'tests/config'; +import { WidgetService } from 'tests/pages/widget.service'; + +export const REFUSE_CF_BLOCK_COOKIE = [ + { + name: process.env.REFUSE_CF_BLOCK_NAME || '', + value: process.env.REFUSE_CF_BLOCK_VALUE || '', + path: '/', + domain: '.testnet.fi', // @TODO: must to changing to hoodi + }, +]; + +type Fixtures = object; + +export const test = base.extend< + Fixtures, + { + browserWithWallet: BrowserService; + widgetService: WidgetService; + } +>({ + page: async ({ page }, use) => { + if (process.env.REFUSE_CF_BLOCK_NAME && process.env.REFUSE_CF_BLOCK_VALUE) { + await page.context().addCookies(REFUSE_CF_BLOCK_COOKIE); + } + await use(page); + }, + browserWithWallet: [ + // eslint-disable-next-line + async ({}, use) => { + const browserService = new BrowserService({ + networkConfig: widgetFullConfig.standConfig.networkConfig, + walletConfig: widgetFullConfig.walletConfig, + nodeConfig: { rpcUrlToMock: '**/api/rpc?chainId=1' }, + enableBrowserContext: true, + }); + await browserService.initWalletSetup(); + + await use(browserService); + // Teardown will be call only when all tests done or when test failed. + await browserService.teardown(); + }, + { scope: 'worker' }, + ], + widgetService: [ + async ({ browserWithWallet }, use) => { + await use( + new WidgetService( + await browserWithWallet.getBrowserContextPage(), + browserWithWallet.getWalletPage(), + ), + ); + }, + { scope: 'worker' }, + ], +}); + +/** + * Skip a specific test before run. Playwright will not run the test with call `skipIf` if condition will be true. + * + * To skip a test use: + * - `test(title, {tag: ['@tag'], ...skipIf()}, callback)` + * + * Example: + * + * ```ts + * test( + * 'Title of test', + * { + * tag: ['@tag'], + * ...skipIf( + * WIDGET_CONFIG.STAND_CONFIG.chainId !== 1, + * "Holesky and others doesn't support ENS", + * ), + * }, + * async () => { + * console.log(); + * }, + * ); + *``` + * + * @param condition - Test is marked as "skipped" when the condition is `true`. + * @param message - Message that will be reflected in a test. + * @returns + */ +export const skipIf = (condition: boolean, message: string) => { + return condition + ? { + annotation: { + type: 'skip', + description: message, + }, + } + : {}; +}; diff --git a/yarn.lock b/yarn.lock index 5fc0ddbc..643576d8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7,6 +7,11 @@ resolved "https://registry.yarnpkg.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf" integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA== +"@adraffy/ens-normalize@^1.10.1": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.11.0.tgz#42cc67c5baa407ac25059fcd7d405cc5ecdb0c33" + integrity sha512-/3DDPKHqqIqxUULp8yP4zODUY1i+2xvVWsv8A79xGWdCAG+8sb0hRh0Rk2QyOJUnnbyPUAZYcpBuRe3nS2OIUg== + "@ampproject/remapping@^2.2.0": version "2.2.1" resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.1.tgz#99e8e11851128b8702cd57c33684f1d0f260b630" @@ -1331,6 +1336,13 @@ resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310" integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== +"@babel/runtime@^7.15.4": + version "7.27.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.27.0.tgz#fbee7cf97c709518ecc1f590984481d5460d4762" + integrity sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/runtime@^7.25.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7": version "7.26.10" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.26.10.tgz#a07b4d8fa27af131a633d7b3524db803eb4764c2" @@ -1744,6 +1756,21 @@ "@ethersproject/properties" "^5.7.0" "@ethersproject/strings" "^5.7.0" +"@ethersproject/abi@5.8.0", "@ethersproject/abi@^5.8.0": + version "5.8.0" + resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.8.0.tgz#e79bb51940ac35fe6f3262d7fe2cdb25ad5f07d9" + integrity sha512-b9YS/43ObplgyV6SlyQsG53/vkSal0MNA1fskSC4mbnCMi8R+NkcH8K9FPYNESf6jUefBUniE4SOKms0E/KK1Q== + dependencies: + "@ethersproject/address" "^5.8.0" + "@ethersproject/bignumber" "^5.8.0" + "@ethersproject/bytes" "^5.8.0" + "@ethersproject/constants" "^5.8.0" + "@ethersproject/hash" "^5.8.0" + "@ethersproject/keccak256" "^5.8.0" + "@ethersproject/logger" "^5.8.0" + "@ethersproject/properties" "^5.8.0" + "@ethersproject/strings" "^5.8.0" + "@ethersproject/abstract-provider@5.7.0", "@ethersproject/abstract-provider@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/abstract-provider/-/abstract-provider-5.7.0.tgz#b0a8550f88b6bf9d51f90e4795d48294630cb9ef" @@ -1757,6 +1784,19 @@ "@ethersproject/transactions" "^5.7.0" "@ethersproject/web" "^5.7.0" +"@ethersproject/abstract-provider@5.8.0", "@ethersproject/abstract-provider@^5.8.0": + version "5.8.0" + resolved "https://registry.yarnpkg.com/@ethersproject/abstract-provider/-/abstract-provider-5.8.0.tgz#7581f9be601afa1d02b95d26b9d9840926a35b0c" + integrity sha512-wC9SFcmh4UK0oKuLJQItoQdzS/qZ51EJegK6EmAWlh+OptpQ/npECOR3QqECd8iGHC0RJb4WKbVdSfif4ammrg== + dependencies: + "@ethersproject/bignumber" "^5.8.0" + "@ethersproject/bytes" "^5.8.0" + "@ethersproject/logger" "^5.8.0" + "@ethersproject/networks" "^5.8.0" + "@ethersproject/properties" "^5.8.0" + "@ethersproject/transactions" "^5.8.0" + "@ethersproject/web" "^5.8.0" + "@ethersproject/abstract-signer@5.7.0", "@ethersproject/abstract-signer@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/abstract-signer/-/abstract-signer-5.7.0.tgz#13f4f32117868452191a4649723cb086d2b596b2" @@ -1768,6 +1808,17 @@ "@ethersproject/logger" "^5.7.0" "@ethersproject/properties" "^5.7.0" +"@ethersproject/abstract-signer@5.8.0", "@ethersproject/abstract-signer@^5.8.0": + version "5.8.0" + resolved "https://registry.yarnpkg.com/@ethersproject/abstract-signer/-/abstract-signer-5.8.0.tgz#8d7417e95e4094c1797a9762e6789c7356db0754" + integrity sha512-N0XhZTswXcmIZQdYtUnd79VJzvEwXQw6PK0dTl9VoYrEBxxCPXqS0Eod7q5TNKRxe1/5WUMuR0u0nqTF/avdCA== + dependencies: + "@ethersproject/abstract-provider" "^5.8.0" + "@ethersproject/bignumber" "^5.8.0" + "@ethersproject/bytes" "^5.8.0" + "@ethersproject/logger" "^5.8.0" + "@ethersproject/properties" "^5.8.0" + "@ethersproject/address@5.7.0", "@ethersproject/address@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/address/-/address-5.7.0.tgz#19b56c4d74a3b0a46bfdbb6cfcc0a153fc697f37" @@ -1779,6 +1830,17 @@ "@ethersproject/logger" "^5.7.0" "@ethersproject/rlp" "^5.7.0" +"@ethersproject/address@5.8.0", "@ethersproject/address@^5.8.0": + version "5.8.0" + resolved "https://registry.yarnpkg.com/@ethersproject/address/-/address-5.8.0.tgz#3007a2c352eee566ad745dca1dbbebdb50a6a983" + integrity sha512-GhH/abcC46LJwshoN+uBNoKVFPxUuZm6dA257z0vZkKmU1+t8xTn8oK7B9qrj8W2rFRMch4gbJl6PmVxjxBEBA== + dependencies: + "@ethersproject/bignumber" "^5.8.0" + "@ethersproject/bytes" "^5.8.0" + "@ethersproject/keccak256" "^5.8.0" + "@ethersproject/logger" "^5.8.0" + "@ethersproject/rlp" "^5.8.0" + "@ethersproject/base64@5.7.0", "@ethersproject/base64@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/base64/-/base64-5.7.0.tgz#ac4ee92aa36c1628173e221d0d01f53692059e1c" @@ -1786,6 +1848,13 @@ dependencies: "@ethersproject/bytes" "^5.7.0" +"@ethersproject/base64@5.8.0", "@ethersproject/base64@^5.8.0": + version "5.8.0" + resolved "https://registry.yarnpkg.com/@ethersproject/base64/-/base64-5.8.0.tgz#61c669c648f6e6aad002c228465d52ac93ee83eb" + integrity sha512-lN0oIwfkYj9LbPx4xEkie6rAMJtySbpOAFXSDVQaBnAzYfB4X2Qr+FXJGxMoc3Bxp2Sm8OwvzMrywxyw0gLjIQ== + dependencies: + "@ethersproject/bytes" "^5.8.0" + "@ethersproject/basex@5.7.0", "@ethersproject/basex@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/basex/-/basex-5.7.0.tgz#97034dc7e8938a8ca943ab20f8a5e492ece4020b" @@ -1794,6 +1863,14 @@ "@ethersproject/bytes" "^5.7.0" "@ethersproject/properties" "^5.7.0" +"@ethersproject/basex@5.8.0", "@ethersproject/basex@^5.8.0": + version "5.8.0" + resolved "https://registry.yarnpkg.com/@ethersproject/basex/-/basex-5.8.0.tgz#1d279a90c4be84d1c1139114a1f844869e57d03a" + integrity sha512-PIgTszMlDRmNwW9nhS6iqtVfdTAKosA7llYXNmGPw4YAI1PUyMv28988wAb41/gHF/WqGdoLv0erHaRcHRKW2Q== + dependencies: + "@ethersproject/bytes" "^5.8.0" + "@ethersproject/properties" "^5.8.0" + "@ethersproject/bignumber@5.7.0", "@ethersproject/bignumber@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/bignumber/-/bignumber-5.7.0.tgz#e2f03837f268ba655ffba03a57853e18a18dc9c2" @@ -1803,6 +1880,15 @@ "@ethersproject/logger" "^5.7.0" bn.js "^5.2.1" +"@ethersproject/bignumber@5.8.0", "@ethersproject/bignumber@^5.8.0": + version "5.8.0" + resolved "https://registry.yarnpkg.com/@ethersproject/bignumber/-/bignumber-5.8.0.tgz#c381d178f9eeb370923d389284efa19f69efa5d7" + integrity sha512-ZyaT24bHaSeJon2tGPKIiHszWjD/54Sz8t57Toch475lCLljC6MgPmxk7Gtzz+ddNN5LuHea9qhAe0x3D+uYPA== + dependencies: + "@ethersproject/bytes" "^5.8.0" + "@ethersproject/logger" "^5.8.0" + bn.js "^5.2.1" + "@ethersproject/bytes@5.7.0", "@ethersproject/bytes@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/bytes/-/bytes-5.7.0.tgz#a00f6ea8d7e7534d6d87f47188af1148d71f155d" @@ -1810,6 +1896,13 @@ dependencies: "@ethersproject/logger" "^5.7.0" +"@ethersproject/bytes@5.8.0", "@ethersproject/bytes@^5.8.0": + version "5.8.0" + resolved "https://registry.yarnpkg.com/@ethersproject/bytes/-/bytes-5.8.0.tgz#9074820e1cac7507a34372cadeb035461463be34" + integrity sha512-vTkeohgJVCPVHu5c25XWaWQOZ4v+DkGoC42/TS2ond+PARCxTJvgTFUNDZovyQ/uAQ4EcpqqowKydcdmRKjg7A== + dependencies: + "@ethersproject/logger" "^5.8.0" + "@ethersproject/constants@5.7.0", "@ethersproject/constants@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/constants/-/constants-5.7.0.tgz#df80a9705a7e08984161f09014ea012d1c75295e" @@ -1817,6 +1910,13 @@ dependencies: "@ethersproject/bignumber" "^5.7.0" +"@ethersproject/constants@5.8.0", "@ethersproject/constants@^5.8.0": + version "5.8.0" + resolved "https://registry.yarnpkg.com/@ethersproject/constants/-/constants-5.8.0.tgz#12f31c2f4317b113a4c19de94e50933648c90704" + integrity sha512-wigX4lrf5Vu+axVTIvNsuL6YrV4O5AXl5ubcURKMEME5TnWBouUh0CDTWxZ2GpnRn1kcCgE7l8O5+VbV9QTTcg== + dependencies: + "@ethersproject/bignumber" "^5.8.0" + "@ethersproject/contracts@5.7.0", "@ethersproject/contracts@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/contracts/-/contracts-5.7.0.tgz#c305e775abd07e48aa590e1a877ed5c316f8bd1e" @@ -1833,6 +1933,22 @@ "@ethersproject/properties" "^5.7.0" "@ethersproject/transactions" "^5.7.0" +"@ethersproject/contracts@5.8.0": + version "5.8.0" + resolved "https://registry.yarnpkg.com/@ethersproject/contracts/-/contracts-5.8.0.tgz#243a38a2e4aa3e757215ea64e276f8a8c9d8ed73" + integrity sha512-0eFjGz9GtuAi6MZwhb4uvUM216F38xiuR0yYCjKJpNfSEy4HUM8hvqqBj9Jmm0IUz8l0xKEhWwLIhPgxNY0yvQ== + dependencies: + "@ethersproject/abi" "^5.8.0" + "@ethersproject/abstract-provider" "^5.8.0" + "@ethersproject/abstract-signer" "^5.8.0" + "@ethersproject/address" "^5.8.0" + "@ethersproject/bignumber" "^5.8.0" + "@ethersproject/bytes" "^5.8.0" + "@ethersproject/constants" "^5.8.0" + "@ethersproject/logger" "^5.8.0" + "@ethersproject/properties" "^5.8.0" + "@ethersproject/transactions" "^5.8.0" + "@ethersproject/hash@5.7.0", "@ethersproject/hash@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/hash/-/hash-5.7.0.tgz#eb7aca84a588508369562e16e514b539ba5240a7" @@ -1848,6 +1964,21 @@ "@ethersproject/properties" "^5.7.0" "@ethersproject/strings" "^5.7.0" +"@ethersproject/hash@5.8.0", "@ethersproject/hash@^5.8.0": + version "5.8.0" + resolved "https://registry.yarnpkg.com/@ethersproject/hash/-/hash-5.8.0.tgz#b8893d4629b7f8462a90102572f8cd65a0192b4c" + integrity sha512-ac/lBcTbEWW/VGJij0CNSw/wPcw9bSRgCB0AIBz8CvED/jfvDoV9hsIIiWfvWmFEi8RcXtlNwp2jv6ozWOsooA== + dependencies: + "@ethersproject/abstract-signer" "^5.8.0" + "@ethersproject/address" "^5.8.0" + "@ethersproject/base64" "^5.8.0" + "@ethersproject/bignumber" "^5.8.0" + "@ethersproject/bytes" "^5.8.0" + "@ethersproject/keccak256" "^5.8.0" + "@ethersproject/logger" "^5.8.0" + "@ethersproject/properties" "^5.8.0" + "@ethersproject/strings" "^5.8.0" + "@ethersproject/hdnode@5.7.0", "@ethersproject/hdnode@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/hdnode/-/hdnode-5.7.0.tgz#e627ddc6b466bc77aebf1a6b9e47405ca5aef9cf" @@ -1866,6 +1997,24 @@ "@ethersproject/transactions" "^5.7.0" "@ethersproject/wordlists" "^5.7.0" +"@ethersproject/hdnode@5.8.0", "@ethersproject/hdnode@^5.8.0": + version "5.8.0" + resolved "https://registry.yarnpkg.com/@ethersproject/hdnode/-/hdnode-5.8.0.tgz#a51ae2a50bcd48ef6fd108c64cbae5e6ff34a761" + integrity sha512-4bK1VF6E83/3/Im0ERnnUeWOY3P1BZml4ZD3wcH8Ys0/d1h1xaFt6Zc+Dh9zXf9TapGro0T4wvO71UTCp3/uoA== + dependencies: + "@ethersproject/abstract-signer" "^5.8.0" + "@ethersproject/basex" "^5.8.0" + "@ethersproject/bignumber" "^5.8.0" + "@ethersproject/bytes" "^5.8.0" + "@ethersproject/logger" "^5.8.0" + "@ethersproject/pbkdf2" "^5.8.0" + "@ethersproject/properties" "^5.8.0" + "@ethersproject/sha2" "^5.8.0" + "@ethersproject/signing-key" "^5.8.0" + "@ethersproject/strings" "^5.8.0" + "@ethersproject/transactions" "^5.8.0" + "@ethersproject/wordlists" "^5.8.0" + "@ethersproject/json-wallets@5.7.0", "@ethersproject/json-wallets@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/json-wallets/-/json-wallets-5.7.0.tgz#5e3355287b548c32b368d91014919ebebddd5360" @@ -1885,6 +2034,25 @@ aes-js "3.0.0" scrypt-js "3.0.1" +"@ethersproject/json-wallets@5.8.0", "@ethersproject/json-wallets@^5.8.0": + version "5.8.0" + resolved "https://registry.yarnpkg.com/@ethersproject/json-wallets/-/json-wallets-5.8.0.tgz#d18de0a4cf0f185f232eb3c17d5e0744d97eb8c9" + integrity sha512-HxblNck8FVUtNxS3VTEYJAcwiKYsBIF77W15HufqlBF9gGfhmYOJtYZp8fSDZtn9y5EaXTE87zDwzxRoTFk11w== + dependencies: + "@ethersproject/abstract-signer" "^5.8.0" + "@ethersproject/address" "^5.8.0" + "@ethersproject/bytes" "^5.8.0" + "@ethersproject/hdnode" "^5.8.0" + "@ethersproject/keccak256" "^5.8.0" + "@ethersproject/logger" "^5.8.0" + "@ethersproject/pbkdf2" "^5.8.0" + "@ethersproject/properties" "^5.8.0" + "@ethersproject/random" "^5.8.0" + "@ethersproject/strings" "^5.8.0" + "@ethersproject/transactions" "^5.8.0" + aes-js "3.0.0" + scrypt-js "3.0.1" + "@ethersproject/keccak256@5.7.0", "@ethersproject/keccak256@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/keccak256/-/keccak256-5.7.0.tgz#3186350c6e1cd6aba7940384ec7d6d9db01f335a" @@ -1893,11 +2061,24 @@ "@ethersproject/bytes" "^5.7.0" js-sha3 "0.8.0" +"@ethersproject/keccak256@5.8.0", "@ethersproject/keccak256@^5.8.0": + version "5.8.0" + resolved "https://registry.yarnpkg.com/@ethersproject/keccak256/-/keccak256-5.8.0.tgz#d2123a379567faf2d75d2aaea074ffd4df349e6a" + integrity sha512-A1pkKLZSz8pDaQ1ftutZoaN46I6+jvuqugx5KYNeQOPqq+JZ0Txm7dlWesCHB5cndJSu5vP2VKptKf7cksERng== + dependencies: + "@ethersproject/bytes" "^5.8.0" + js-sha3 "0.8.0" + "@ethersproject/logger@5.7.0", "@ethersproject/logger@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.7.0.tgz#6ce9ae168e74fecf287be17062b590852c311892" integrity sha512-0odtFdXu/XHtjQXJYA3u9G0G8btm0ND5Cu8M7i5vhEcE8/HmF4Lbdqanwyv4uQTr2tx6b7fQRmgLrsnpQlmnig== +"@ethersproject/logger@5.8.0", "@ethersproject/logger@^5.8.0": + version "5.8.0" + resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.8.0.tgz#f0232968a4f87d29623a0481690a2732662713d6" + integrity sha512-Qe6knGmY+zPPWTC+wQrpitodgBfH7XoceCGL5bJVejmH+yCS3R8jJm8iiWuvWbG76RUmyEG53oqv6GMVWqunjA== + "@ethersproject/networks@5.7.1", "@ethersproject/networks@^5.7.0": version "5.7.1" resolved "https://registry.yarnpkg.com/@ethersproject/networks/-/networks-5.7.1.tgz#118e1a981d757d45ccea6bb58d9fd3d9db14ead6" @@ -1905,6 +2086,13 @@ dependencies: "@ethersproject/logger" "^5.7.0" +"@ethersproject/networks@5.8.0", "@ethersproject/networks@^5.8.0": + version "5.8.0" + resolved "https://registry.yarnpkg.com/@ethersproject/networks/-/networks-5.8.0.tgz#8b4517a3139380cba9fb00b63ffad0a979671fde" + integrity sha512-egPJh3aPVAzbHwq8DD7Po53J4OUSsA1MjQp8Vf/OZPav5rlmWUaFLiq8cvQiGK0Z5K6LYzm29+VA/p4RL1FzNg== + dependencies: + "@ethersproject/logger" "^5.8.0" + "@ethersproject/pbkdf2@5.7.0", "@ethersproject/pbkdf2@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/pbkdf2/-/pbkdf2-5.7.0.tgz#d2267d0a1f6e123f3771007338c47cccd83d3102" @@ -1913,6 +2101,14 @@ "@ethersproject/bytes" "^5.7.0" "@ethersproject/sha2" "^5.7.0" +"@ethersproject/pbkdf2@5.8.0", "@ethersproject/pbkdf2@^5.8.0": + version "5.8.0" + resolved "https://registry.yarnpkg.com/@ethersproject/pbkdf2/-/pbkdf2-5.8.0.tgz#cd2621130e5dd51f6a0172e63a6e4a0c0a0ec37e" + integrity sha512-wuHiv97BrzCmfEaPbUFpMjlVg/IDkZThp9Ri88BpjRleg4iePJaj2SW8AIyE8cXn5V1tuAaMj6lzvsGJkGWskg== + dependencies: + "@ethersproject/bytes" "^5.8.0" + "@ethersproject/sha2" "^5.8.0" + "@ethersproject/properties@5.7.0", "@ethersproject/properties@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/properties/-/properties-5.7.0.tgz#a6e12cb0439b878aaf470f1902a176033067ed30" @@ -1920,6 +2116,13 @@ dependencies: "@ethersproject/logger" "^5.7.0" +"@ethersproject/properties@5.8.0", "@ethersproject/properties@^5.8.0": + version "5.8.0" + resolved "https://registry.yarnpkg.com/@ethersproject/properties/-/properties-5.8.0.tgz#405a8affb6311a49a91dabd96aeeae24f477020e" + integrity sha512-PYuiEoQ+FMaZZNGrStmN7+lWjlsoufGIHdww7454FIaGdbe/p5rnaCXTr5MtBYl3NkeoVhHZuyzChPeGeKIpQw== + dependencies: + "@ethersproject/logger" "^5.8.0" + "@ethersproject/providers@5.7.2", "@ethersproject/providers@^5.7.0", "@ethersproject/providers@^5.7.2": version "5.7.2" resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.7.2.tgz#f8b1a4f275d7ce58cf0a2eec222269a08beb18cb" @@ -1946,6 +2149,32 @@ bech32 "1.1.4" ws "7.4.6" +"@ethersproject/providers@5.8.0": + version "5.8.0" + resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.8.0.tgz#6c2ae354f7f96ee150439f7de06236928bc04cb4" + integrity sha512-3Il3oTzEx3o6kzcg9ZzbE+oCZYyY+3Zh83sKkn4s1DZfTUjIegHnN2Cm0kbn9YFy45FDVcuCLLONhU7ny0SsCw== + dependencies: + "@ethersproject/abstract-provider" "^5.8.0" + "@ethersproject/abstract-signer" "^5.8.0" + "@ethersproject/address" "^5.8.0" + "@ethersproject/base64" "^5.8.0" + "@ethersproject/basex" "^5.8.0" + "@ethersproject/bignumber" "^5.8.0" + "@ethersproject/bytes" "^5.8.0" + "@ethersproject/constants" "^5.8.0" + "@ethersproject/hash" "^5.8.0" + "@ethersproject/logger" "^5.8.0" + "@ethersproject/networks" "^5.8.0" + "@ethersproject/properties" "^5.8.0" + "@ethersproject/random" "^5.8.0" + "@ethersproject/rlp" "^5.8.0" + "@ethersproject/sha2" "^5.8.0" + "@ethersproject/strings" "^5.8.0" + "@ethersproject/transactions" "^5.8.0" + "@ethersproject/web" "^5.8.0" + bech32 "1.1.4" + ws "8.18.0" + "@ethersproject/random@5.7.0", "@ethersproject/random@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/random/-/random-5.7.0.tgz#af19dcbc2484aae078bb03656ec05df66253280c" @@ -1954,6 +2183,14 @@ "@ethersproject/bytes" "^5.7.0" "@ethersproject/logger" "^5.7.0" +"@ethersproject/random@5.8.0", "@ethersproject/random@^5.8.0": + version "5.8.0" + resolved "https://registry.yarnpkg.com/@ethersproject/random/-/random-5.8.0.tgz#1bced04d49449f37c6437c701735a1a022f0057a" + integrity sha512-E4I5TDl7SVqyg4/kkA/qTfuLWAQGXmSOgYyO01So8hLfwgKvYK5snIlzxJMk72IFdG/7oh8yuSqY2KX7MMwg+A== + dependencies: + "@ethersproject/bytes" "^5.8.0" + "@ethersproject/logger" "^5.8.0" + "@ethersproject/rlp@5.7.0", "@ethersproject/rlp@^5.5.0", "@ethersproject/rlp@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/rlp/-/rlp-5.7.0.tgz#de39e4d5918b9d74d46de93af80b7685a9c21304" @@ -1962,6 +2199,14 @@ "@ethersproject/bytes" "^5.7.0" "@ethersproject/logger" "^5.7.0" +"@ethersproject/rlp@5.8.0", "@ethersproject/rlp@^5.8.0": + version "5.8.0" + resolved "https://registry.yarnpkg.com/@ethersproject/rlp/-/rlp-5.8.0.tgz#5a0d49f61bc53e051532a5179472779141451de5" + integrity sha512-LqZgAznqDbiEunaUvykH2JAoXTT9NV0Atqk8rQN9nx9SEgThA/WMx5DnW8a9FOufo//6FZOCHZ+XiClzgbqV9Q== + dependencies: + "@ethersproject/bytes" "^5.8.0" + "@ethersproject/logger" "^5.8.0" + "@ethersproject/sha2@5.7.0", "@ethersproject/sha2@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/sha2/-/sha2-5.7.0.tgz#9a5f7a7824ef784f7f7680984e593a800480c9fb" @@ -1971,6 +2216,15 @@ "@ethersproject/logger" "^5.7.0" hash.js "1.1.7" +"@ethersproject/sha2@5.8.0", "@ethersproject/sha2@^5.8.0": + version "5.8.0" + resolved "https://registry.yarnpkg.com/@ethersproject/sha2/-/sha2-5.8.0.tgz#8954a613bb78dac9b46829c0a95de561ef74e5e1" + integrity sha512-dDOUrXr9wF/YFltgTBYS0tKslPEKr6AekjqDW2dbn1L1xmjGR+9GiKu4ajxovnrDbwxAKdHjW8jNcwfz8PAz4A== + dependencies: + "@ethersproject/bytes" "^5.8.0" + "@ethersproject/logger" "^5.8.0" + hash.js "1.1.7" + "@ethersproject/signing-key@5.7.0", "@ethersproject/signing-key@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/signing-key/-/signing-key-5.7.0.tgz#06b2df39411b00bc57c7c09b01d1e41cf1b16ab3" @@ -1983,6 +2237,18 @@ elliptic "6.5.4" hash.js "1.1.7" +"@ethersproject/signing-key@5.8.0", "@ethersproject/signing-key@^5.8.0": + version "5.8.0" + resolved "https://registry.yarnpkg.com/@ethersproject/signing-key/-/signing-key-5.8.0.tgz#9797e02c717b68239c6349394ea85febf8893119" + integrity sha512-LrPW2ZxoigFi6U6aVkFN/fa9Yx/+4AtIUe4/HACTvKJdhm0eeb107EVCIQcrLZkxaSIgc/eCrX8Q1GtbH+9n3w== + dependencies: + "@ethersproject/bytes" "^5.8.0" + "@ethersproject/logger" "^5.8.0" + "@ethersproject/properties" "^5.8.0" + bn.js "^5.2.1" + elliptic "6.6.1" + hash.js "1.1.7" + "@ethersproject/solidity@5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/solidity/-/solidity-5.7.0.tgz#5e9c911d8a2acce2a5ebb48a5e2e0af20b631cb8" @@ -1995,6 +2261,18 @@ "@ethersproject/sha2" "^5.7.0" "@ethersproject/strings" "^5.7.0" +"@ethersproject/solidity@5.8.0": + version "5.8.0" + resolved "https://registry.yarnpkg.com/@ethersproject/solidity/-/solidity-5.8.0.tgz#429bb9fcf5521307a9448d7358c26b93695379b9" + integrity sha512-4CxFeCgmIWamOHwYN9d+QWGxye9qQLilpgTU0XhYs1OahkclF+ewO+3V1U0mvpiuQxm5EHHmv8f7ClVII8EHsA== + dependencies: + "@ethersproject/bignumber" "^5.8.0" + "@ethersproject/bytes" "^5.8.0" + "@ethersproject/keccak256" "^5.8.0" + "@ethersproject/logger" "^5.8.0" + "@ethersproject/sha2" "^5.8.0" + "@ethersproject/strings" "^5.8.0" + "@ethersproject/strings@5.7.0", "@ethersproject/strings@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/strings/-/strings-5.7.0.tgz#54c9d2a7c57ae8f1205c88a9d3a56471e14d5ed2" @@ -2004,6 +2282,15 @@ "@ethersproject/constants" "^5.7.0" "@ethersproject/logger" "^5.7.0" +"@ethersproject/strings@5.8.0", "@ethersproject/strings@^5.8.0": + version "5.8.0" + resolved "https://registry.yarnpkg.com/@ethersproject/strings/-/strings-5.8.0.tgz#ad79fafbf0bd272d9765603215ac74fd7953908f" + integrity sha512-qWEAk0MAvl0LszjdfnZ2uC8xbR2wdv4cDabyHiBh3Cldq/T8dPH3V4BbBsAYJUeonwD+8afVXld274Ls+Y1xXg== + dependencies: + "@ethersproject/bytes" "^5.8.0" + "@ethersproject/constants" "^5.8.0" + "@ethersproject/logger" "^5.8.0" + "@ethersproject/transactions@5.7.0", "@ethersproject/transactions@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/transactions/-/transactions-5.7.0.tgz#91318fc24063e057885a6af13fdb703e1f993d3b" @@ -2019,6 +2306,21 @@ "@ethersproject/rlp" "^5.7.0" "@ethersproject/signing-key" "^5.7.0" +"@ethersproject/transactions@5.8.0", "@ethersproject/transactions@^5.8.0": + version "5.8.0" + resolved "https://registry.yarnpkg.com/@ethersproject/transactions/-/transactions-5.8.0.tgz#1e518822403abc99def5a043d1c6f6fe0007e46b" + integrity sha512-UglxSDjByHG0TuU17bDfCemZ3AnKO2vYrL5/2n2oXvKzvb7Cz+W9gOWXKARjp2URVwcWlQlPOEQyAviKwT4AHg== + dependencies: + "@ethersproject/address" "^5.8.0" + "@ethersproject/bignumber" "^5.8.0" + "@ethersproject/bytes" "^5.8.0" + "@ethersproject/constants" "^5.8.0" + "@ethersproject/keccak256" "^5.8.0" + "@ethersproject/logger" "^5.8.0" + "@ethersproject/properties" "^5.8.0" + "@ethersproject/rlp" "^5.8.0" + "@ethersproject/signing-key" "^5.8.0" + "@ethersproject/units@5.7.0", "@ethersproject/units@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/units/-/units-5.7.0.tgz#637b563d7e14f42deeee39245275d477aae1d8b1" @@ -2028,6 +2330,15 @@ "@ethersproject/constants" "^5.7.0" "@ethersproject/logger" "^5.7.0" +"@ethersproject/units@5.8.0": + version "5.8.0" + resolved "https://registry.yarnpkg.com/@ethersproject/units/-/units-5.8.0.tgz#c12f34ba7c3a2de0e9fa0ed0ee32f3e46c5c2c6a" + integrity sha512-lxq0CAnc5kMGIiWW4Mr041VT8IhNM+Pn5T3haO74XZWFulk7wH1Gv64HqE96hT4a7iiNMdOCFEBgaxWuk8ETKQ== + dependencies: + "@ethersproject/bignumber" "^5.8.0" + "@ethersproject/constants" "^5.8.0" + "@ethersproject/logger" "^5.8.0" + "@ethersproject/wallet@5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/wallet/-/wallet-5.7.0.tgz#4e5d0790d96fe21d61d38fb40324e6c7ef350b2d" @@ -2049,6 +2360,27 @@ "@ethersproject/transactions" "^5.7.0" "@ethersproject/wordlists" "^5.7.0" +"@ethersproject/wallet@5.8.0": + version "5.8.0" + resolved "https://registry.yarnpkg.com/@ethersproject/wallet/-/wallet-5.8.0.tgz#49c300d10872e6986d953e8310dc33d440da8127" + integrity sha512-G+jnzmgg6UxurVKRKvw27h0kvG75YKXZKdlLYmAHeF32TGUzHkOFd7Zn6QHOTYRFWnfjtSSFjBowKo7vfrXzPA== + dependencies: + "@ethersproject/abstract-provider" "^5.8.0" + "@ethersproject/abstract-signer" "^5.8.0" + "@ethersproject/address" "^5.8.0" + "@ethersproject/bignumber" "^5.8.0" + "@ethersproject/bytes" "^5.8.0" + "@ethersproject/hash" "^5.8.0" + "@ethersproject/hdnode" "^5.8.0" + "@ethersproject/json-wallets" "^5.8.0" + "@ethersproject/keccak256" "^5.8.0" + "@ethersproject/logger" "^5.8.0" + "@ethersproject/properties" "^5.8.0" + "@ethersproject/random" "^5.8.0" + "@ethersproject/signing-key" "^5.8.0" + "@ethersproject/transactions" "^5.8.0" + "@ethersproject/wordlists" "^5.8.0" + "@ethersproject/web@5.7.1", "@ethersproject/web@^5.7.0": version "5.7.1" resolved "https://registry.yarnpkg.com/@ethersproject/web/-/web-5.7.1.tgz#de1f285b373149bee5928f4eb7bcb87ee5fbb4ae" @@ -2060,6 +2392,17 @@ "@ethersproject/properties" "^5.7.0" "@ethersproject/strings" "^5.7.0" +"@ethersproject/web@5.8.0", "@ethersproject/web@^5.8.0": + version "5.8.0" + resolved "https://registry.yarnpkg.com/@ethersproject/web/-/web-5.8.0.tgz#3e54badc0013b7a801463a7008a87988efce8a37" + integrity sha512-j7+Ksi/9KfGviws6Qtf9Q7KCqRhpwrYKQPs+JBA/rKVFF/yaWLHJEH3zfVP2plVu+eys0d2DlFmhoQJayFewcw== + dependencies: + "@ethersproject/base64" "^5.8.0" + "@ethersproject/bytes" "^5.8.0" + "@ethersproject/logger" "^5.8.0" + "@ethersproject/properties" "^5.8.0" + "@ethersproject/strings" "^5.8.0" + "@ethersproject/wordlists@5.7.0", "@ethersproject/wordlists@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/wordlists/-/wordlists-5.7.0.tgz#8fb2c07185d68c3e09eb3bfd6e779ba2774627f5" @@ -2071,6 +2414,36 @@ "@ethersproject/properties" "^5.7.0" "@ethersproject/strings" "^5.7.0" +"@ethersproject/wordlists@5.8.0", "@ethersproject/wordlists@^5.8.0": + version "5.8.0" + resolved "https://registry.yarnpkg.com/@ethersproject/wordlists/-/wordlists-5.8.0.tgz#7a5654ee8d1bb1f4dbe43f91d217356d650ad821" + integrity sha512-2df9bbXicZws2Sb5S6ET493uJ0Z84Fjr3pC4tu/qlnZERibZCeUVuqdtt+7Tv9xxhUxHoIekIA7avrKUWHrezg== + dependencies: + "@ethersproject/bytes" "^5.8.0" + "@ethersproject/hash" "^5.8.0" + "@ethersproject/logger" "^5.8.0" + "@ethersproject/properties" "^5.8.0" + "@ethersproject/strings" "^5.8.0" + +"@ganache/console.log@^0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@ganache/console.log/-/console.log-0.2.0.tgz#32ea0df806ed735d61bd0537d7b7fc350e511479" + integrity sha512-+SNBUZzrbe4DE4F0jdl9SU8w3ek5k4cUE73ttUFweo8FaKEDQsMbFjZ3ZU0LM6QM/zCMqE7euSq0s/IlsYxf7A== + dependencies: + "@ganache/utils" "0.3.0" + ethereumjs-util "7.1.5" + +"@ganache/utils@0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@ganache/utils/-/utils-0.3.0.tgz#f95d7a4746d4e062febf3ce59f65f6ca1336be8a" + integrity sha512-cxoG8KQxkYPl71BPdKZihjVKqN2AE7WLXjU65BVOQ5jEYrUH3CWSxA9v7CCUJj4e0HoXFpVFIZ+1HRkiBKKiKg== + dependencies: + emittery "0.10.0" + keccak "3.0.1" + seedrandom "3.0.5" + optionalDependencies: + "@trufflesuite/bigint-buffer" "1.1.9" + "@humanwhocodes/config-array@^0.11.14": version "0.11.14" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.14.tgz#d78e481a039f7566ecc9660b4ea7fe6b1fec442b" @@ -2167,6 +2540,13 @@ "@types/node" "*" jest-mock "^29.7.0" +"@jest/expect-utils@^28.1.3": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-28.1.3.tgz#58561ce5db7cd253a7edddbc051fb39dda50f525" + integrity sha512-wvbi9LUrHJLn3NlDW6wF2hvIMtd4JUl2QNVrjq+IBSHirgfrR3o9RnVtxzdEGO2n9JyIWwHnLfby5KzqBGg2YA== + dependencies: + jest-get-type "^28.0.2" + "@jest/expect-utils@^29.7.0": version "29.7.0" resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.7.0.tgz#023efe5d26a8a70f21677d0a1afc0f0a44e3a1c6" @@ -2298,6 +2678,18 @@ slash "^3.0.0" write-file-atomic "^4.0.2" +"@jest/types@^28.1.3": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-28.1.3.tgz#b05de80996ff12512bc5ceb1d208285a7d11748b" + integrity sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ== + dependencies: + "@jest/schemas" "^28.1.3" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^17.0.8" + chalk "^4.0.0" + "@jest/types@^29.6.3": version "29.6.3" resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59" @@ -2626,6 +3018,16 @@ resolved "https://registry.yarnpkg.com/@lidofinance/api-rpc/-/api-rpc-0.47.0.tgz#2367eec1c8363516bde48b632a6fbe32aec33a1e" integrity sha512-6xx2O89OhWAxaob+SqXEX2xtrumG4MCUrclooCIvAhz64Lp3Wa0Hzt2lgJyFMhJxpDPMj3LmYNbuB15lwEQKwg== +"@lidofinance/browser-service@1.0.0-alpha-move-br.3": + version "1.0.0-alpha-move-br.3" + resolved "https://registry.yarnpkg.com/@lidofinance/browser-service/-/browser-service-1.0.0-alpha-move-br.3.tgz#634e792f320e6288fe9c3b4da6752cb9b93d72b5" + integrity sha512-OVtXhEFQJJyHwj6O6xjUJ3pSOyLNZyp8pac0qwmFH7OWe+Y0Oy06xexLgJr89mN/9p1MLQiTSGmRRqNhnl0FAg== + dependencies: + "@lidofinance/wallets-testing-extensions" "1.1.1-alpha-move-br.1" + "@lidofinance/wallets-testing-nodes" "1.1.5-alpha-move-br.1" + "@lidofinance/wallets-testing-wallets" "1.30.0-alpha-move-br.1" + "@playwright/test" "^1.51.1" + "@lidofinance/eslint-config@^0.43.0": version "0.43.0" resolved "https://registry.yarnpkg.com/@lidofinance/eslint-config/-/eslint-config-0.43.0.tgz#4841f6c3a89e16520ab053a0873c462d806cd341" @@ -2712,6 +3114,42 @@ resolved "https://registry.yarnpkg.com/@lidofinance/utils/-/utils-1.3.2.tgz#63ba5be298bc415c20edcd9d930319e496199068" integrity sha512-sP1ZhAoWz9OpU1S5jzDzl0bnwKXZDBT1RLFFKDBW/+YkoxYAlFReyuA/w5R+I0iYjlBkdzkZZReNM/XzIO4YXg== +"@lidofinance/wallets-testing-extensions@1.1.1-alpha-move-br.1": + version "1.1.1-alpha-move-br.1" + resolved "https://registry.yarnpkg.com/@lidofinance/wallets-testing-extensions/-/wallets-testing-extensions-1.1.1-alpha-move-br.1.tgz#907e9738743cc0d0a2ae3bc3189caebc7db3061d" + integrity sha512-nOV8myMre9PvdoWg8Juv5loSc0hyHd8HY2ND1cXwKXF5mK6z0utNa8Et/ELCckh7Qqus7iUOa2VFe3A9aBpJkA== + dependencies: + "@nestjs/common" "^10.0.0" + "@playwright/test" "^1.51.1" + axios "^0.27.2" + reflect-metadata "^0.1.13" + rxjs "^7.5.6" + unzipper "^0.10.11" + +"@lidofinance/wallets-testing-nodes@1.1.5-alpha-move-br.1": + version "1.1.5-alpha-move-br.1" + resolved "https://registry.yarnpkg.com/@lidofinance/wallets-testing-nodes/-/wallets-testing-nodes-1.1.5-alpha-move-br.1.tgz#28e429571e23967743d8148f6dfee08a11598edf" + integrity sha512-twkCfNdfMjHsLpxBYca4rrR0XsyJLWinUk20hVuDq5BqWufo43ebCFsOVZzMcgjfSRqiUDyHC4XVGyo0lW55lg== + dependencies: + "@ganache/console.log" "^0.2.0" + "@nestjs/common" "^10.0.0" + "@playwright/test" "^1.51.1" + ethers "^5.6.9" + ganache "7.9.2" + reflect-metadata "^0.1.13" + rxjs "^7.5.6" + +"@lidofinance/wallets-testing-wallets@1.30.0-alpha-move-br.1": + version "1.30.0-alpha-move-br.1" + resolved "https://registry.yarnpkg.com/@lidofinance/wallets-testing-wallets/-/wallets-testing-wallets-1.30.0-alpha-move-br.1.tgz#e3851ab2ff327da79ad7463e761e4ce1d0f88553" + integrity sha512-cdo5dX2PBD1gN7QggpN6tdNSTERg3yfKj3TePfv1ca+G7aY/odAYlyI6QawpQHryGq4SnhIIMlbETvftUpGtlg== + dependencies: + "@playwright/test" "^1.51.1" + expect "^28.1.3" + reflect-metadata "^0.1.13" + rxjs "^7.5.6" + viem "^2.21.40" + "@lit-labs/ssr-dom-shim@^1.0.0", "@lit-labs/ssr-dom-shim@^1.1.0": version "1.1.2" resolved "https://registry.yarnpkg.com/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.1.2.tgz#d693d972974a354034454ec1317eb6afd0b00312" @@ -2724,6 +3162,11 @@ dependencies: "@lit-labs/ssr-dom-shim" "^1.0.0" +"@lukeed/csprng@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@lukeed/csprng/-/csprng-1.1.0.tgz#1e3e4bd05c1cc7a0b2ddbd8a03f39f6e4b5e6cfe" + integrity sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA== + "@metamask/safe-event-emitter@2.0.0", "@metamask/safe-event-emitter@^2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@metamask/safe-event-emitter/-/safe-event-emitter-2.0.0.tgz#af577b477c683fad17c619a78208cede06f9605c" @@ -2808,6 +3251,24 @@ "@motionone/dom" "^10.16.4" tslib "^2.3.1" +"@nestjs/common@^10.0.0": + version "10.4.15" + resolved "https://registry.yarnpkg.com/@nestjs/common/-/common-10.4.15.tgz#27c291466d9100eb86fdbe6f7bbb4d1a6ad55f70" + integrity sha512-vaLg1ZgwhG29BuLDxPA9OAcIlgqzp9/N8iG0wGapyUNTf4IY4O6zAHgN6QalwLhFxq7nOI021vdRojR1oF3bqg== + dependencies: + uid "2.0.2" + iterare "1.2.1" + tslib "2.8.1" + +"@nestjs/common@^11.0.12": + version "11.0.13" + resolved "https://registry.yarnpkg.com/@nestjs/common/-/common-11.0.13.tgz#532cd172d5b5fa4e7da471c83e6945e0a1ebd80b" + integrity sha512-cXqXJPQTcJIYqT8GtBYqjYY9sklCBqp/rh9z1R40E60gWnsU598YIQWkojSFRI9G7lT/+uF+jqSrg/CMPBk7QQ== + dependencies: + uid "2.0.2" + iterare "1.2.1" + tslib "2.8.1" + "@next/bundle-analyzer@^13.2.4": version "13.5.6" resolved "https://registry.yarnpkg.com/@next/bundle-analyzer/-/bundle-analyzer-13.5.6.tgz#3c73f2e15ff5507317b37b87ce984bac5a5d7ad0" @@ -2892,6 +3353,13 @@ resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-12.3.4.tgz#d28ea15a72cdcf96201c60a43e9630cd7fda168f" integrity sha512-DQ20JEfTBZAgF8QCjYfJhv2/279M6onxFjdG/+5B0Cyj00/EdBxiWb2eGGFgQhrBbNv/lsvzFbbi0Ptf8Vw/bg== +"@noble/curves@1.8.1", "@noble/curves@^1.6.0", "@noble/curves@~1.8.1": + version "1.8.1" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.8.1.tgz#19bc3970e205c99e4bdb1c64a4785706bce497ff" + integrity sha512-warwspo+UYUPep0Q+vtdVB4Ugn8GGQj8iyB3gnRWsztmUHTI3S1nhdiWNsPUGL0vud7JlRRk1XEu7Lq1KGTnMQ== + dependencies: + "@noble/hashes" "1.7.1" + "@noble/curves@^1.4.2": version "1.5.0" resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.5.0.tgz#7a9b9b507065d516e6dce275a1e31db8d2a100dd" @@ -2904,7 +3372,7 @@ resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.4.0.tgz#45814aa329f30e4fe0ba49426f49dfccdd066426" integrity sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg== -"@noble/hashes@^1.1.2": +"@noble/hashes@1.7.1", "@noble/hashes@^1.1.2", "@noble/hashes@^1.5.0", "@noble/hashes@~1.7.1": version "1.7.1" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.7.1.tgz#5738f6d765710921e7a751e00c20ae091ed8db0f" integrity sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ== @@ -3032,12 +3500,12 @@ "@parcel/watcher-win32-ia32" "2.4.0" "@parcel/watcher-win32-x64" "2.4.0" -"@playwright/test@^1.29.2": - version "1.43.1" - resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.43.1.tgz#16728a59eb8ce0f60472f98d8886d6cab0fa3e42" - integrity sha512-HgtQzFgNEEo4TE22K/X7sYTYNqEMMTZmFS8kTq6m8hXj+m1D8TgwgIbumHddJa9h4yl4GkKb8/bgAl2+g7eDgA== +"@playwright/test@^1.51.1": + version "1.51.1" + resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.51.1.tgz#75357d513221a7be0baad75f01e966baf9c41a2e" + integrity sha512-nM+kEaTSAoVlXmMPH10017vn3FSiFqr/bh4fKg9vmAdMfd9SDqRZNvPSiAHADc/itWak+qPvMPZQOPwCBW7k7Q== dependencies: - playwright "1.43.1" + playwright "1.51.1" "@polka/url@^1.0.0-next.20": version "1.0.0-next.24" @@ -3246,6 +3714,28 @@ resolved "https://registry.yarnpkg.com/@safe-global/safe-gateway-typescript-sdk/-/safe-gateway-typescript-sdk-3.14.0.tgz#9581c524c1ea4956555f40761eb6b4007392aa82" integrity sha512-/dqU66RvHw50n+7x3nwnJedq8V6iLQyoWitNdjx5cFTBmae+rpP+LvHq+LqZfXJVkB1qNytMdjFjdyES0t79gQ== +"@scure/base@~1.2.2", "@scure/base@~1.2.4": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.2.4.tgz#002eb571a35d69bdb4c214d0995dff76a8dcd2a9" + integrity sha512-5Yy9czTO47mqz+/J8GM6GIId4umdCk1wc1q8rKERQulIoc8VP9pzDcghv10Tl2E7R96ZUx/PhND3ESYUQX8NuQ== + +"@scure/bip32@1.6.2", "@scure/bip32@^1.5.0": + version "1.6.2" + resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.6.2.tgz#093caa94961619927659ed0e711a6e4bf35bffd0" + integrity sha512-t96EPDMbtGgtb7onKKqxRLfE5g05k7uHnHRM2xdE6BP/ZmxaLtPek4J4KfVn/90IQNrU1IOAqMgiDtUdtbe3nw== + dependencies: + "@noble/curves" "~1.8.1" + "@noble/hashes" "~1.7.1" + "@scure/base" "~1.2.2" + +"@scure/bip39@1.5.4", "@scure/bip39@^1.4.0": + version "1.5.4" + resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.5.4.tgz#07fd920423aa671be4540d59bdd344cc1461db51" + integrity sha512-TFM4ni0vKvCfBpohoh+/lY05i9gRbSwXWngAsF4CABQxoaOHijxuaZ2R6cStDQ5CHtHO9aGJTr4ksVJASRRyMA== + dependencies: + "@noble/hashes" "~1.7.1" + "@scure/base" "~1.2.4" + "@sinclair/typebox@^0.24.1": version "0.24.51" resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.24.51.tgz#645f33fe4e02defe26f2f5c0410e1c094eac7f5f" @@ -3693,6 +4183,30 @@ "@tanstack/query-core" "4.36.1" use-sync-external-store "^1.2.0" +"@trufflesuite/bigint-buffer@1.1.10": + version "1.1.10" + resolved "https://registry.yarnpkg.com/@trufflesuite/bigint-buffer/-/bigint-buffer-1.1.10.tgz#a1d9ca22d3cad1a138b78baaf15543637a3e1692" + integrity sha512-pYIQC5EcMmID74t26GCC67946mgTJFiLXOT/BYozgrd4UEY2JHEGLhWi9cMiQCt5BSqFEvKkCHNnoj82SRjiEw== + dependencies: + node-gyp-build "4.4.0" + +"@trufflesuite/bigint-buffer@1.1.9": + version "1.1.9" + resolved "https://registry.yarnpkg.com/@trufflesuite/bigint-buffer/-/bigint-buffer-1.1.9.tgz#e2604d76e1e4747b74376d68f1312f9944d0d75d" + integrity sha512-bdM5cEGCOhDSwminryHJbRmXc1x7dPKg6Pqns3qyTwFlxsqUgxE29lsERS3PlIW1HTjoIGMUqsk1zQQwST1Yxw== + dependencies: + node-gyp-build "4.3.0" + +"@trufflesuite/uws-js-unofficial@20.30.0-unofficial.0": + version "20.30.0-unofficial.0" + resolved "https://registry.yarnpkg.com/@trufflesuite/uws-js-unofficial/-/uws-js-unofficial-20.30.0-unofficial.0.tgz#2fbc2f8ef7e82fbeea6abaf7e8a9d42a02b479d3" + integrity sha512-r5X0aOQcuT6pLwTRLD+mPnAM/nlKtvIK4Z+My++A8tTOR0qTjNRx8UB8jzRj3D+p9PMAp5LnpCUUGmz7/TppwA== + dependencies: + ws "8.13.0" + optionalDependencies: + bufferutil "4.0.7" + utf-8-validate "6.0.3" + "@trysound/sax@0.2.0": version "0.2.0" resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad" @@ -3759,6 +4273,13 @@ dependencies: "@babel/types" "^7.20.7" +"@types/bn.js@^5.1.0": + version "5.1.6" + resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-5.1.6.tgz#9ba818eec0c85e4d3c679518428afdf611d03203" + integrity sha512-Xh8vSwUeMKeYYrj3cX4lGQgFSF/N03r+tv4AiLl1SucqV+uTQpxRcnM8AkXKHwYP9ZPXOYXRr2KPXpVlIvqh9w== + dependencies: + "@types/node" "*" + "@types/connect@^3.4.33": version "3.4.38" resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.38.tgz#5ba7f3bc4fbbdeaff8dded952e5ff2cc53f8d858" @@ -3835,6 +4356,11 @@ resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.0.tgz#d774355e41f372d5350a4d0714abb48194a489c3" integrity sha512-t7dhREVv6dbNj0q17X12j7yDG4bD/DHYX7o5/DbDxobP0HnGPgpRz2Ej77aL7TZT3DSw13fqUTj8J4mMnqa7WA== +"@types/lru-cache@5.1.1": + version "5.1.1" + resolved "https://registry.yarnpkg.com/@types/lru-cache/-/lru-cache-5.1.1.tgz#c48c2e27b65d2a153b19bfc1a317e30872e01eef" + integrity sha512-ssE3Vlrys7sdIzs5LOxCzTVMsU7i9oa/IaW92wF32JFb3CVczqOkru2xspuKczHEbG3nvmPY7IFqVmGGHdNbYw== + "@types/mdast@^3.0.0": version "3.0.15" resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.15.tgz#49c524a263f30ffa28b71ae282f813ed000ab9f5" @@ -3891,6 +4417,13 @@ resolved "https://registry.yarnpkg.com/@types/nprogress/-/nprogress-0.2.3.tgz#b2150b054a13622fabcba12cf6f0b54c48b14287" integrity sha512-k7kRA033QNtC+gLc4VPlfnue58CM1iQLgn1IMAU8VPHGOj7oIHPp9UlhedEnD/Gl8evoCjwkZjlBORtZ3JByUA== +"@types/pbkdf2@^3.0.0": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@types/pbkdf2/-/pbkdf2-3.1.2.tgz#2dc43808e9985a2c69ff02e2d2027bd4fe33e8dc" + integrity sha512-uRwJqmiXmh9++aSu1VNEn3iIxWOhd8AHXNSdlaLfdAAdSTY9jYVeGWnzejM3dvrkbqE3/hyQkQQ29IFATEGlew== + dependencies: + "@types/node" "*" + "@types/prettier@^2.1.1": version "2.7.3" resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.3.tgz#3e51a17e291d01d17d3fc61422015a933af7a08f" @@ -3946,6 +4479,18 @@ resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.23.0.tgz#0a6655b3e2708eaabca00b7372fafd7a792a7b09" integrity sha512-YIoDCTH3Af6XM5VuwGG/QL/CJqga1Zm3NkU3HZ4ZHK2fRMPYP1VczsTUqtsf43PH/iJNVlPHAo2oWX7BSdB2Hw== +"@types/secp256k1@^4.0.1": + version "4.0.6" + resolved "https://registry.yarnpkg.com/@types/secp256k1/-/secp256k1-4.0.6.tgz#d60ba2349a51c2cbc5e816dcd831a42029d376bf" + integrity sha512-hHxJU6PAEUn0TP4S/ZOzuTUvJWuZ6eIKeNKb5RBpODvSl6hp1Wrw4s7ATY50rklRCScUDpHzVA/DQdSjJ3UoYQ== + dependencies: + "@types/node" "*" + +"@types/seedrandom@3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/seedrandom/-/seedrandom-3.0.1.tgz#1254750a4fec4aff2ebec088ccd0bb02e91fedb4" + integrity sha512-giB9gzDeiCeloIXDgzFBCgjj1k4WxcDrZtGl6h1IqmUPlxF+Nx8Ve+96QCyDZ/HseB/uvDsKbpib9hU5cU53pw== + "@types/semver@^7.3.12", "@types/semver@^7.5.0": version "7.5.8" resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.8.tgz#8268a8c57a3e4abd25c165ecd36237db7948a55e" @@ -4567,6 +5112,11 @@ JSONStream@^1.3.5: jsonparse "^1.2.0" through ">=2.2.7 <3" +abitype@1.0.8, abitype@^1.0.6: + version "1.0.8" + resolved "https://registry.yarnpkg.com/abitype/-/abitype-1.0.8.tgz#3554f28b2e9d6e9f35eb59878193eabd1b9f46ba" + integrity sha512-ZeiI6h3GnW06uYDLx0etQtX/p8E24UaHHBj57RSjK7YBFe7iuVn07EDpOeP451D06sF27VOz9JJPlIKJmXgkEg== + abitype@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/abitype/-/abitype-0.3.0.tgz#75150e337d88cc0b2423ed0d3fc36935f139d04c" @@ -4579,6 +5129,31 @@ abort-controller@^3.0.0: dependencies: event-target-shim "^5.0.0" +abstract-level@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/abstract-level/-/abstract-level-1.0.3.tgz#78a67d3d84da55ee15201486ab44c09560070741" + integrity sha512-t6jv+xHy+VYwc4xqZMn2Pa9DjcdzvzZmQGRjTFc8spIbRGHgBrEKbPq+rYXc7CCo0lxgYvSgKVg9qZAhpVQSjA== + dependencies: + buffer "^6.0.3" + catering "^2.1.0" + is-buffer "^2.0.5" + level-supports "^4.0.0" + level-transcoder "^1.0.1" + module-error "^1.0.1" + queue-microtask "^1.2.3" + +abstract-leveldown@7.2.0, abstract-leveldown@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/abstract-leveldown/-/abstract-leveldown-7.2.0.tgz#08d19d4e26fb5be426f7a57004851b39e1795a2e" + integrity sha512-DnhQwcFEaYsvYDnACLZhMmCWd3rkOeEvglpa4q5i/5Jlm3UIsWaxVzuXvDLFCSCWRO3yy2/+V/G7FusFgejnfQ== + dependencies: + buffer "^6.0.3" + catering "^2.0.0" + is-buffer "^2.0.5" + level-concat-iterator "^3.0.0" + level-supports "^2.0.1" + queue-microtask "^1.2.3" + acorn-jsx@^5.3.2: version "5.3.2" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" @@ -4626,6 +5201,16 @@ ajv@^6.12.4, ajv@^6.12.5: json-schema-traverse "^0.4.1" uri-js "^4.2.2" +ajv@^8.0.0, ajv@^8.12.0: + version "8.17.1" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.17.1.tgz#37d9a5c776af6bc92d7f4f9510eba4c0a60d11a6" + integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g== + dependencies: + fast-deep-equal "^3.1.3" + fast-uri "^3.0.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + ajv@^8.11.0: version "8.12.0" resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.12.0.tgz#d1a0527323e22f53562c567c00991577dfbe19d1" @@ -4865,6 +5450,13 @@ ast-types-flow@^0.0.8: resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.8.tgz#0a85e1c92695769ac13a428bb653e7538bea27d6" integrity sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ== +async-eventemitter@0.2.4: + version "0.2.4" + resolved "https://registry.yarnpkg.com/async-eventemitter/-/async-eventemitter-0.2.4.tgz#f5e7c8ca7d3e46aab9ec40a292baf686a0bafaca" + integrity sha512-pd20BwL7Yt1zwDFy+8MX8F1+WCT8aQeKj0kQnTrH9WaeRETlRamVhD0JtRPmrV4GfOJ2F9CvdQkZeZhnh2TuHw== + dependencies: + async "^2.4.0" + async-mutex@^0.2.6: version "0.2.6" resolved "https://registry.yarnpkg.com/async-mutex/-/async-mutex-0.2.6.tgz#0d7a3deb978bc2b984d5908a2038e1ae2e54ff40" @@ -4872,6 +5464,20 @@ async-mutex@^0.2.6: dependencies: tslib "^2.0.0" +async-mutex@~0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/async-mutex/-/async-mutex-0.5.0.tgz#353c69a0b9e75250971a64ac203b0ebfddd75482" + integrity sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA== + dependencies: + tslib "^2.4.0" + +async@^2.4.0: + version "2.6.4" + resolved "https://registry.yarnpkg.com/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221" + integrity sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA== + dependencies: + lodash "^4.17.14" + async@^3.2.3: version "3.2.5" resolved "https://registry.yarnpkg.com/async/-/async-3.2.5.tgz#ebd52a8fdaf7a2289a24df399f8d8485c8a46b66" @@ -4904,6 +5510,14 @@ axe-core@^4.9.1: resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.10.0.tgz#d9e56ab0147278272739a000880196cdfe113b59" integrity sha512-Mr2ZakwQ7XUAjp7pAwQWRhhK8mQQ6JAaNWSjmjxil0R8BPioMtQsTLOolGYkji1rcL++3dCqZA3zWqpT+9Ew6g== +axios-retry@^3.5.0: + version "3.9.1" + resolved "https://registry.yarnpkg.com/axios-retry/-/axios-retry-3.9.1.tgz#c8924a8781c8e0a2c5244abf773deb7566b3830d" + integrity sha512-8PJDLJv7qTTMMwdnbMvrLYuvB47M81wRtxQmEdV5w4rgbTXTt+vtPkXwajOfOdSyv/wZICJOC+/UhXH4aQ/R+w== + dependencies: + "@babel/runtime" "^7.15.4" + is-retry-allowed "^2.2.0" + axios@0.26.1: version "0.26.1" resolved "https://registry.yarnpkg.com/axios/-/axios-0.26.1.tgz#1ede41c51fcf51bbbd6fd43669caaa4f0495aaa9" @@ -4911,6 +5525,14 @@ axios@0.26.1: dependencies: follow-redirects "^1.14.8" +axios@^0.27.2: + version "0.27.2" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.27.2.tgz#207658cc8621606e586c85db4b41a750e756d972" + integrity sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ== + dependencies: + follow-redirects "^1.14.9" + form-data "^4.0.0" + axios@^1.3.4, axios@^1.6.0: version "1.7.5" resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.5.tgz#21eed340eb5daf47d29b6e002424b3e88c8c54b1" @@ -4920,6 +5542,15 @@ axios@^1.3.4, axios@^1.6.0: form-data "^4.0.0" proxy-from-env "^1.1.0" +axios@^1.8.2: + version "1.8.4" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.8.4.tgz#78990bb4bc63d2cae072952d374835950a82f447" + integrity sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw== + dependencies: + follow-redirects "^1.15.6" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + axobject-query@~3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-3.1.1.tgz#3b6e5c6d4e43ca7ba51c5babf99d22a9c68485e1" @@ -5049,6 +5680,11 @@ bech32@1.1.4: resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.4.tgz#e38c9f37bf179b8eb16ae3a772b40c356d4832e9" integrity sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ== +big-integer@^1.6.17: + version "1.6.52" + resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.52.tgz#60a887f3047614a8e1bffe5d7173490a97dc8c85" + integrity sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg== + big.js@^5.2.2: version "5.2.2" resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" @@ -5071,6 +5707,14 @@ binary-extensions@^2.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== +binary@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/binary/-/binary-0.3.0.tgz#9f60553bc5ce8c3386f3b553cff47462adecaa79" + integrity sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg== + dependencies: + buffers "~0.1.1" + chainsaw "~0.1.0" + bind-decorator@^1.0.11: version "1.0.11" resolved "https://registry.yarnpkg.com/bind-decorator/-/bind-decorator-1.0.11.tgz#e41bc06a1f65dd9cec476c91c5daf3978488252f" @@ -5088,12 +5732,22 @@ bintrees@1.0.2: resolved "https://registry.yarnpkg.com/bintrees/-/bintrees-1.0.2.tgz#49f896d6e858a4a499df85c38fb399b9aff840f8" integrity sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw== +blakejs@^1.1.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/blakejs/-/blakejs-1.2.1.tgz#5057e4206eadb4a97f7c0b6e197a505042fc3814" + integrity sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ== + +bluebird@~3.4.1: + version "3.4.7" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.4.7.tgz#f72d760be09b7f76d08ed8fae98b289a8d05fab3" + integrity sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA== + bn.js@^4.11.9: version "4.12.0" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== -bn.js@^5.1.1, bn.js@^5.2.0, bn.js@^5.2.1: +bn.js@^5.1.1, bn.js@^5.1.2, bn.js@^5.2.0, bn.js@^5.2.1: version "5.2.1" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== @@ -5139,6 +5793,18 @@ brorand@^1.1.0: resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" integrity sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w== +browserify-aes@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" + integrity sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA== + dependencies: + buffer-xor "^1.0.3" + cipher-base "^1.0.0" + create-hash "^1.1.0" + evp_bytestokey "^1.0.3" + inherits "^2.0.1" + safe-buffer "^5.0.1" + browserslist@^4.22.2: version "4.22.3" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.22.3.tgz#299d11b7e947a6b843981392721169e27d60c5a6" @@ -5204,6 +5870,16 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== +buffer-indexof-polyfill@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz#d2732135c5999c64b277fcf9b1abe3498254729c" + integrity sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A== + +buffer-xor@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" + integrity sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ== + buffer@6.0.3, buffer@^6.0.3, buffer@~6.0.3: version "6.0.3" resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" @@ -5212,6 +5888,25 @@ buffer@6.0.3, buffer@^6.0.3, buffer@~6.0.3: base64-js "^1.3.1" ieee754 "^1.2.1" +buffers@~0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/buffers/-/buffers-0.1.1.tgz#b24579c3bed4d6d396aeee6d9a8ae7f5482ab7bb" + integrity sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ== + +bufferutil@4.0.5: + version "4.0.5" + resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.0.5.tgz#da9ea8166911cc276bf677b8aed2d02d31f59028" + integrity sha512-HTm14iMQKK2FjFLRTM5lAVcyaUzOnqbPtesFIvREgXpJHdQm8bWS+GkQgIkfaBYRHuCnea7w8UVNfwiAQhlr9A== + dependencies: + node-gyp-build "^4.3.0" + +bufferutil@4.0.7: + version "4.0.7" + resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.0.7.tgz#60c0d19ba2c992dd8273d3f73772ffc894c153ad" + integrity sha512-kukuqc39WOHtdxtw4UScxF/WVnMFVSQVKhtx3AjZJzhd0RGZZldcrfSEbVsWWe6KNH253574cq5F+wpv0G9pJw== + dependencies: + node-gyp-build "^4.3.0" + bufferutil@^4.0.1: version "4.0.8" resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.0.8.tgz#1de6a71092d65d7766c4d8a522b261a6e787e8ea" @@ -5269,11 +5964,23 @@ caniuse-lite@^1.0.30001406, caniuse-lite@^1.0.30001580, caniuse-lite@^1.0.300016 resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001651.tgz" integrity sha512-9Cf+Xv1jJNe1xPZLGuUXLNkE1BoDkqRqYyFJ9TDYSqhduqA4hu4oR9HluGoWYQC/aj8WHjsGVV+bwkh0+tegRg== +catering@^2.0.0, catering@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/catering/-/catering-2.1.1.tgz#66acba06ed5ee28d5286133982a927de9a04b510" + integrity sha512-K7Qy8O9p76sL3/3m7/zLKbRkyOlSZAgzEaLhyj2mXS8PsCud2Eo4hAb8aLtZqHh0QGqLcb9dlJSu6lHRVENm1w== + ccount@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/ccount/-/ccount-1.1.0.tgz#246687debb6014735131be8abab2d93898f8d043" integrity sha512-vlNK021QdI7PNeiUh/lKkC/mNHHfV0m/Ad5JoI0TYtlBnJAslM/JIkm/tGC88bkLIwO6OQ5uV6ztS6kVAtCDlg== +chainsaw@~0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/chainsaw/-/chainsaw-0.1.0.tgz#5eab50b28afe58074d0d58291388828b5e5fbc98" + integrity sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ== + dependencies: + traverse ">=0.3.0 <0.4" + chalk@5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.3.0.tgz#67c20a7ebef70e7f3970a01f90fa210cb6860385" @@ -5288,7 +5995,7 @@ chalk@^2.4.2: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^4.0.0, chalk@^4.1.0: +chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -5346,6 +6053,14 @@ ci-info@^3.2.0, ci-info@^3.8.0: resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== +cipher-base@^1.0.0, cipher-base@^1.0.3: + version "1.0.6" + resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.6.tgz#8fe672437d01cd6c4561af5334e0cc50ff1955f7" + integrity sha512-3Ek9H3X6pj5TgenXYtNWdaBon1tgYCaebd+XPg0keyjEbEfkD4KkmAxkQ/i1vYvxdcT5nscLBfq9VJRmCBcFSw== + dependencies: + inherits "^2.0.4" + safe-buffer "^5.2.1" + cipher-base@^1.0.1: version "1.0.4" resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" @@ -5618,6 +6333,11 @@ core-js-compat@^3.36.1, core-js-compat@^3.37.1: dependencies: browserslist "^4.23.0" +core-util-is@~1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" + integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== + cors@^2.8.5: version "2.8.5" resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" @@ -5641,7 +6361,7 @@ cosmiconfig@^8.0.0, cosmiconfig@^8.1.3: parse-json "^5.2.0" path-type "^4.0.0" -create-hash@^1.1.0: +create-hash@^1.1.0, create-hash@^1.1.2, create-hash@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg== @@ -5652,6 +6372,18 @@ create-hash@^1.1.0: ripemd160 "^2.0.1" sha.js "^2.4.0" +create-hmac@^1.1.4, create-hmac@^1.1.7: + version "1.1.7" + resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff" + integrity sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg== + dependencies: + cipher-base "^1.0.3" + create-hash "^1.1.0" + inherits "^2.0.1" + ripemd160 "^2.0.0" + safe-buffer "^5.0.1" + sha.js "^2.4.8" + create-jest@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/create-jest/-/create-jest-29.7.0.tgz#a355c5b3cb1e1af02ba177fe7afd7feee49a5320" @@ -6024,6 +6756,23 @@ dot-prop@^5.1.0: dependencies: is-obj "^2.0.0" +dotenv-expand@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-10.0.0.tgz#12605d00fb0af6d0a592e6558585784032e4ef37" + integrity sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A== + +dotenv@^16.0.0, dotenv@^16.4.7: + version "16.4.7" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.7.tgz#0e20c5b82950140aa99be360a8a5f52335f53c26" + integrity sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ== + +duplexer2@~0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" + integrity sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA== + dependencies: + readable-stream "^2.0.2" + duplexer@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" @@ -6079,6 +6828,24 @@ elliptic@6.5.4: minimalistic-assert "^1.0.1" minimalistic-crypto-utils "^1.0.1" +elliptic@6.6.1, elliptic@^6.5.4, elliptic@^6.5.7: + version "6.6.1" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.6.1.tgz#3b8ffb02670bf69e382c7f65bf524c97c5405c06" + integrity sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g== + dependencies: + bn.js "^4.11.9" + brorand "^1.1.0" + hash.js "^1.0.0" + hmac-drbg "^1.0.1" + inherits "^2.0.4" + minimalistic-assert "^1.0.1" + minimalistic-crypto-utils "^1.0.1" + +emittery@0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.10.0.tgz#bb373c660a9d421bb44706ec4967ed50c02a8026" + integrity sha512-AGvFfs+d0JKCJQ4o01ASQLGPmSCxgfU9RFXvzPvZdjKK8oscynksuJhWrSTSw7j7Ep/sZct5b5ZhYCi8S/t0HQ== + emittery@^0.13.1: version "0.13.1" resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad" @@ -6129,6 +6896,15 @@ entities@^4.2.0, entities@^4.4.0: resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== +env-schema@^5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/env-schema/-/env-schema-5.2.1.tgz#7af7d5464690f18c862c21f9b1fe662d3ae6485a" + integrity sha512-gWMNrQ3dVHAZcCx7epiFwgXcyfBh4heD/6+OK3bEbke3uL+KqwYA9nUOwzJyRZh1cJOFcwdPuY1n0GKSFlSWAg== + dependencies: + ajv "^8.0.0" + dotenv "^16.0.0" + dotenv-expand "^10.0.0" + error-ex@^1.3.1: version "1.3.2" resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" @@ -6609,6 +7385,38 @@ eth-rpc-errors@^4.0.2: dependencies: fast-safe-stringify "^2.0.6" +ethereum-cryptography@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz#8d6143cfc3d74bf79bbd8edecdf29e4ae20dd191" + integrity sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ== + dependencies: + "@types/pbkdf2" "^3.0.0" + "@types/secp256k1" "^4.0.1" + blakejs "^1.1.0" + browserify-aes "^1.2.0" + bs58check "^2.1.2" + create-hash "^1.2.0" + create-hmac "^1.1.7" + hash.js "^1.1.7" + keccak "^3.0.0" + pbkdf2 "^3.0.17" + randombytes "^2.1.0" + safe-buffer "^5.1.2" + scrypt-js "^3.0.0" + secp256k1 "^4.0.1" + setimmediate "^1.0.5" + +ethereumjs-util@7.1.5: + version "7.1.5" + resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz#9ecf04861e4fbbeed7465ece5f23317ad1129181" + integrity sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg== + dependencies: + "@types/bn.js" "^5.1.0" + bn.js "^5.1.2" + create-hash "^1.1.2" + ethereum-cryptography "^0.1.3" + rlp "^2.2.4" + ethers@5.7.2, ethers@^5.7.2: version "5.7.2" resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.7.2.tgz#3a7deeabbb8c030d4126b24f84e525466145872e" @@ -6645,26 +7453,70 @@ ethers@5.7.2, ethers@^5.7.2: "@ethersproject/web" "5.7.1" "@ethersproject/wordlists" "5.7.0" +ethers@^5.6.9: + version "5.8.0" + resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.8.0.tgz#97858dc4d4c74afce83ea7562fe9493cedb4d377" + integrity sha512-DUq+7fHrCg1aPDFCHx6UIPb3nmt2XMpM7Y/g2gLhsl3lIBqeAfOJIl1qEvRf2uq3BiKxmh6Fh5pfp2ieyek7Kg== + dependencies: + "@ethersproject/abi" "5.8.0" + "@ethersproject/abstract-provider" "5.8.0" + "@ethersproject/abstract-signer" "5.8.0" + "@ethersproject/address" "5.8.0" + "@ethersproject/base64" "5.8.0" + "@ethersproject/basex" "5.8.0" + "@ethersproject/bignumber" "5.8.0" + "@ethersproject/bytes" "5.8.0" + "@ethersproject/constants" "5.8.0" + "@ethersproject/contracts" "5.8.0" + "@ethersproject/hash" "5.8.0" + "@ethersproject/hdnode" "5.8.0" + "@ethersproject/json-wallets" "5.8.0" + "@ethersproject/keccak256" "5.8.0" + "@ethersproject/logger" "5.8.0" + "@ethersproject/networks" "5.8.0" + "@ethersproject/pbkdf2" "5.8.0" + "@ethersproject/properties" "5.8.0" + "@ethersproject/providers" "5.8.0" + "@ethersproject/random" "5.8.0" + "@ethersproject/rlp" "5.8.0" + "@ethersproject/sha2" "5.8.0" + "@ethersproject/signing-key" "5.8.0" + "@ethersproject/solidity" "5.8.0" + "@ethersproject/strings" "5.8.0" + "@ethersproject/transactions" "5.8.0" + "@ethersproject/units" "5.8.0" + "@ethersproject/wallet" "5.8.0" + "@ethersproject/web" "5.8.0" + "@ethersproject/wordlists" "5.8.0" + event-target-shim@^5.0.0: version "5.0.1" resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== +eventemitter3@5.0.1, eventemitter3@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4" + integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== + eventemitter3@^4.0.0, eventemitter3@^4.0.7: version "4.0.7" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== -eventemitter3@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4" - integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== - events@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== +evp_bytestokey@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" + integrity sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA== + dependencies: + md5.js "^1.3.4" + safe-buffer "^5.1.1" + execa@7.2.0: version "7.2.0" resolved "https://registry.yarnpkg.com/execa/-/execa-7.2.0.tgz#657e75ba984f42a70f38928cedc87d6f2d4fe4e9" @@ -6715,6 +7567,17 @@ exit@^0.1.2: resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== +expect@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/expect/-/expect-28.1.3.tgz#90a7c1a124f1824133dd4533cce2d2bdcb6603ec" + integrity sha512-eEh0xn8HlsuOBxFgIss+2mX85VAS4Qy3OSkjV7rlBWljtA4oWH37glVGyOZSZvErDT/yBywZdPGwCXuTvSG85g== + dependencies: + "@jest/expect-utils" "^28.1.3" + jest-get-type "^28.0.2" + jest-matcher-utils "^28.1.3" + jest-message-util "^28.1.3" + jest-util "^28.1.3" + expect@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/expect/-/expect-29.7.0.tgz#578874590dcb3214514084c08115d8aee61e11bc" @@ -6793,6 +7656,11 @@ fast-stable-stringify@^1.0.0: resolved "https://registry.yarnpkg.com/fast-stable-stringify/-/fast-stable-stringify-1.0.0.tgz#5c5543462b22aeeefd36d05b34e51c78cb86d313" integrity sha512-wpYMUmFu5f00Sm0cj2pfivpmawLZ0NKdviQ4w9zJeR8JVtOpOxHmLaJuj0vxvGqMJQWyP/COUkF75/57OKyRag== +fast-uri@^3.0.1: + version "3.0.6" + resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.0.6.tgz#88f130b77cfaea2378d56bf970dea21257a68748" + integrity sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw== + fastq@^1.6.0: version "1.17.1" resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47" @@ -6897,6 +7765,11 @@ follow-redirects@^1.14.8, follow-redirects@^1.15.6: resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b" integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== +follow-redirects@^1.14.9: + version "1.15.9" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1" + integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ== + for-each@^0.3.3: version "0.3.3" resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" @@ -6946,6 +7819,16 @@ fsevents@^2.3.2, fsevents@~2.3.2: resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== +fstream@^1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.12.tgz#4e8ba8ee2d48be4f7d0de505455548eae5932045" + integrity sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg== + dependencies: + graceful-fs "^4.1.2" + inherits "~2.0.0" + mkdirp ">=0.5 0" + rimraf "2" + function-bind@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" @@ -6966,6 +7849,27 @@ functions-have-names@^1.2.3: resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== +ganache@7.9.2: + version "7.9.2" + resolved "https://registry.yarnpkg.com/ganache/-/ganache-7.9.2.tgz#77f506ad2735dd9109696ffa1834a9dd2f806449" + integrity sha512-7gsVVDpO9AhrFyDMWWl7SpMsPpqGcnAzjxz3k32LheIPNd64p2XsY9GYRdhWmKuryb60W1iaWPZWDkFKlbRWHA== + dependencies: + "@trufflesuite/bigint-buffer" "1.1.10" + "@trufflesuite/uws-js-unofficial" "20.30.0-unofficial.0" + "@types/bn.js" "^5.1.0" + "@types/lru-cache" "5.1.1" + "@types/seedrandom" "3.0.1" + abstract-level "1.0.3" + abstract-leveldown "7.2.0" + async-eventemitter "0.2.4" + emittery "0.10.0" + keccak "3.0.2" + leveldown "6.1.0" + secp256k1 "4.0.3" + optionalDependencies: + bufferutil "4.0.5" + utf-8-validate "5.0.7" + gensync@^1.0.0-beta.2: version "1.0.0-beta.2" resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" @@ -7117,7 +8021,7 @@ gopd@^1.0.1: dependencies: get-intrinsic "^1.1.3" -graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.9: +graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.2, graceful-fs@^4.2.4, graceful-fs@^4.2.9: version "4.2.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== @@ -7396,7 +8300,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.4: +inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.0, inherits@~2.0.3, inherits@~2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -7534,7 +8438,7 @@ is-boolean-object@^1.1.0: call-bind "^1.0.2" has-tostringtag "^1.0.0" -is-buffer@^2.0.0: +is-buffer@^2.0.0, is-buffer@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.5.tgz#ebc252e400d22ff8d77fa09888821a24a658c191" integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ== @@ -7695,6 +8599,11 @@ is-regex@^1.1.4: call-bind "^1.0.2" has-tostringtag "^1.0.0" +is-retry-allowed@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-2.2.0.tgz#88f34cbd236e043e71b6932d09b0c65fb7b4d71d" + integrity sha512-XVm7LOeLpTW4jV19QSH38vkswxoLud8sQ57YwJVTPWdiaI9I8keEhGFpBlslyVsgdQy4Opg8QOLb8YRgsyZiQg== + is-set@^2.0.2, is-set@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.3.tgz#8ab209ea424608141372ded6e0cb200ef1d9d01d" @@ -7796,6 +8705,11 @@ isarray@^2.0.5: resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== +isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== + isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" @@ -7814,6 +8728,11 @@ isomorphic-ws@^4.0.1: resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz#55fd4cd6c5e6491e76dc125938dd863f5cd4f2dc" integrity sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w== +isows@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/isows/-/isows-1.0.6.tgz#0da29d706fa51551c663c627ace42769850f86e7" + integrity sha512-lPHCayd40oW98/I0uvgaHKWCSvkzY27LjWLbtzOm64yQ+G3Q5npjjbdppU65iZXkK1Zt+kH9pfegli0AYfwYYw== + istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: version "3.2.2" resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" @@ -7867,6 +8786,11 @@ istanbul-reports@^3.1.3: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" +iterare@1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/iterare/-/iterare-1.2.1.tgz#139c400ff7363690e33abffa33cbba8920f00042" + integrity sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q== + iterator.prototype@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/iterator.prototype/-/iterator.prototype-1.1.2.tgz#5e29c8924f01916cb9335f1ff80619dcff22b0c0" @@ -8063,7 +8987,7 @@ jest-leak-detector@^29.7.0: jest-get-type "^29.6.3" pretty-format "^29.7.0" -jest-matcher-utils@^28.0.0: +jest-matcher-utils@^28.0.0, jest-matcher-utils@^28.1.3: version "28.1.3" resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-28.1.3.tgz#5a77f1c129dd5ba3b4d7fc20728806c78893146e" integrity sha512-kQeJ7qHemKfbzKoGjHHrRKH6atgxMk8Enkk2iPQ3XwO6oE/KYD8lMYOziCkeSB9G4adPM4nR1DE8Tf5JeWH6Bw== @@ -8083,6 +9007,21 @@ jest-matcher-utils@^29.7.0: jest-get-type "^29.6.3" pretty-format "^29.7.0" +jest-message-util@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-28.1.3.tgz#232def7f2e333f1eecc90649b5b94b0055e7c43d" + integrity sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g== + dependencies: + "@babel/code-frame" "^7.12.13" + "@jest/types" "^28.1.3" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.9" + micromatch "^4.0.4" + pretty-format "^28.1.3" + slash "^3.0.0" + stack-utils "^2.0.3" + jest-message-util@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.7.0.tgz#8bc392e204e95dfe7564abbe72a404e28e51f7f3" @@ -8221,6 +9160,18 @@ jest-snapshot@^29.7.0: pretty-format "^29.7.0" semver "^7.5.3" +jest-util@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-28.1.3.tgz#f4f932aa0074f0679943220ff9cbba7e497028b0" + integrity sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ== + dependencies: + "@jest/types" "^28.1.3" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + jest-util@^29.0.0, jest-util@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc" @@ -8425,7 +9376,24 @@ jsonschema@^1.4.1: object.assign "^4.1.4" object.values "^1.1.6" -keccak@^3.0.1, keccak@^3.0.3: +keccak@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/keccak/-/keccak-3.0.1.tgz#ae30a0e94dbe43414f741375cff6d64c8bea0bff" + integrity sha512-epq90L9jlFWCW7+pQa6JOnKn2Xgl2mtI664seYR6MHskvI9agt7AnDqmAlp9TqU4/caMYbA08Hi5DMZAl5zdkA== + dependencies: + node-addon-api "^2.0.0" + node-gyp-build "^4.2.0" + +keccak@3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/keccak/-/keccak-3.0.2.tgz#4c2c6e8c54e04f2670ee49fa734eb9da152206e0" + integrity sha512-PyKKjkH53wDMLGrvmRGSNWgmSxZOUqbnXwKL9tmgbFYA1iAYqW21kfR7mZXV0MlESiefxQQE9X9fTa3X+2MPDQ== + dependencies: + node-addon-api "^2.0.0" + node-gyp-build "^4.2.0" + readable-stream "^3.6.0" + +keccak@^3.0.0, keccak@^3.0.1, keccak@^3.0.3: version "3.0.4" resolved "https://registry.yarnpkg.com/keccak/-/keccak-3.0.4.tgz#edc09b89e633c0549da444432ecf062ffadee86d" integrity sha512-3vKuW0jV8J3XNTzvfyicFR5qvxrSAGl7KIhvgOu5cmWwM7tZRj3fMbj/pfIf4be7aznbc+prBWGjywox/g2Y6Q== @@ -8473,6 +9441,40 @@ language-tags@^1.0.9: dependencies: language-subtag-registry "^0.3.20" +level-concat-iterator@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/level-concat-iterator/-/level-concat-iterator-3.1.0.tgz#5235b1f744bc34847ed65a50548aa88d22e881cf" + integrity sha512-BWRCMHBxbIqPxJ8vHOvKUsaO0v1sLYZtjN3K2iZJsRBYtp+ONsY6Jfi6hy9K3+zolgQRryhIn2NRZjZnWJ9NmQ== + dependencies: + catering "^2.1.0" + +level-supports@^2.0.1: + version "2.1.0" + resolved "https://registry.yarnpkg.com/level-supports/-/level-supports-2.1.0.tgz#9af908d853597ecd592293b2fad124375be79c5f" + integrity sha512-E486g1NCjW5cF78KGPrMDRBYzPuueMZ6VBXHT6gC7A8UYWGiM14fGgp+s/L1oFfDWSPV/+SFkYCmZ0SiESkRKA== + +level-supports@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/level-supports/-/level-supports-4.0.1.tgz#431546f9d81f10ff0fea0e74533a0e875c08c66a" + integrity sha512-PbXpve8rKeNcZ9C1mUicC9auIYFyGpkV9/i6g76tLgANwWhtG2v7I4xNBUlkn3lE2/dZF3Pi0ygYGtLc4RXXdA== + +level-transcoder@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/level-transcoder/-/level-transcoder-1.0.1.tgz#f8cef5990c4f1283d4c86d949e73631b0bc8ba9c" + integrity sha512-t7bFwFtsQeD8cl8NIoQ2iwxA0CL/9IFw7/9gAjOonH0PWTTiRfY7Hq+Ejbsxh86tXobDQ6IOiddjNYIfOBs06w== + dependencies: + buffer "^6.0.3" + module-error "^1.0.1" + +leveldown@6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/leveldown/-/leveldown-6.1.0.tgz#7ab1297706f70c657d1a72b31b40323aa612b9ee" + integrity sha512-8C7oJDT44JXxh04aSSsfcMI8YiaGRhOFI9/pMEL7nWJLVsWajDPTRxsSHTM2WcTVY5nXM+SuRHzPPi0GbnDX+w== + dependencies: + abstract-leveldown "^7.2.0" + napi-macros "~2.0.0" + node-gyp-build "^4.3.0" + leven@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" @@ -8512,6 +9514,11 @@ lint-staged@^13.2.3: string-argv "0.3.2" yaml "2.3.1" +listenercount@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/listenercount/-/listenercount-1.0.1.tgz#84c8a72ab59c4725321480c975e6508342e70937" + integrity sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ== + listhen@^1.5.5: version "1.6.0" resolved "https://registry.yarnpkg.com/listhen/-/listhen-1.6.0.tgz#df26c527c59b87557be4d0408d4a09626bd946c8" @@ -8611,6 +9618,11 @@ lodash.defaults@^4.2.0: resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" integrity sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ== +lodash.get@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" + integrity sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ== + lodash.isarguments@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" @@ -8671,7 +9683,7 @@ lodash.upperfirst@^4.3.1: resolved "https://registry.yarnpkg.com/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz#1365edf431480481ef0d1c68957a5ed99d49f7ce" integrity sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg== -lodash@^4.17.12, lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.21: +lodash@^4.17.12, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.21: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -8910,7 +9922,7 @@ mime-db@1.52.0: resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== -mime-types@^2.1.12, mime-types@^2.1.27: +mime-types@^2.1.12, mime-types@^2.1.27, mime-types@^2.1.33: version "2.1.35" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== @@ -8980,6 +9992,13 @@ minimist@^1.2.0, minimist@^1.2.6: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== +"mkdirp@>=0.5 0": + version "0.5.6" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" + integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== + dependencies: + minimist "^1.2.6" + mkdirp@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" @@ -8995,6 +10014,11 @@ mlly@^1.2.0, mlly@^1.5.0: pkg-types "^1.0.3" ufo "^1.3.2" +module-error@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/module-error/-/module-error-1.0.2.tgz#8d1a48897ca883f47a45816d4fb3e3c6ba404d86" + integrity sha512-0yuvsqSCv8LbaOKhnsQ/T5JhyFlCYLPXK3U2sgV10zoKQwzs/MyfuQUOZQ1V/6OCOJsK/TRgNVrPuPDqtdMFtA== + motion@10.16.2: version "10.16.2" resolved "https://registry.yarnpkg.com/motion/-/motion-10.16.2.tgz#7dc173c6ad62210a7e9916caeeaf22c51e598d21" @@ -9042,6 +10066,11 @@ nanoid@^3.3.7: resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.8.tgz#b1be3030bee36aaff18bacb375e5cce521684baf" integrity sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w== +napi-macros@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/napi-macros/-/napi-macros-2.0.0.tgz#2b6bae421e7b96eb687aa6c77a7858640670001b" + integrity sha512-A0xLykHtARfueITVDernsAWdtIMbOJgKgcluwENp3AlsKN/PloyO10HtmoqnFAQAcxPkgZN7wdfPfEd0zNGxbg== + napi-wasm@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/napi-wasm/-/napi-wasm-1.1.0.tgz#bbe617823765ae9c1bc12ff5942370eae7b2ba4e" @@ -9104,6 +10133,11 @@ node-addon-api@^2.0.0: resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-2.0.2.tgz#432cfa82962ce494b132e9d72a15b29f71ff5d32" integrity sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA== +node-addon-api@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-5.1.0.tgz#49da1ca055e109a23d537e9de43c09cca21eb762" + integrity sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA== + node-addon-api@^7.0.0: version "7.1.0" resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-7.1.0.tgz#71f609369379c08e251c558527a107107b5e0fdb" @@ -9126,6 +10160,16 @@ node-forge@^1.3.1: resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3" integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA== +node-gyp-build@4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.3.0.tgz#9f256b03e5826150be39c764bf51e993946d71a3" + integrity sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q== + +node-gyp-build@4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.4.0.tgz#42e99687ce87ddeaf3a10b99dc06abc11021f3f4" + integrity sha512-amJnQCcgtRVw9SvoebO3BKGESClrfXGCUTX9hSn1OuGQTQBOZmVd0Z0OlecpuRksKvbsUqALE8jls/ErClAPuQ== + node-gyp-build@^4.2.0, node-gyp-build@^4.3.0: version "4.8.0" resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.8.0.tgz#3fee9c1731df4581a3f9ead74664369ff00d26dd" @@ -9367,6 +10411,19 @@ os-tmpdir@~1.0.2: resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g== +ox@0.6.9: + version "0.6.9" + resolved "https://registry.yarnpkg.com/ox/-/ox-0.6.9.tgz#da1ee04fa10de30c8d04c15bfb80fe58b1f554bd" + integrity sha512-wi5ShvzE4eOcTwQVsIPdFr+8ycyX+5le/96iAJutaZAvCes1J0+RvpEPg5QDPDiaR0XQQAvZVl7AwqQcINuUug== + dependencies: + "@adraffy/ens-normalize" "^1.10.1" + "@noble/curves" "^1.6.0" + "@noble/hashes" "^1.5.0" + "@scure/bip32" "^1.5.0" + "@scure/bip39" "^1.4.0" + abitype "^1.0.6" + eventemitter3 "5.0.1" + p-limit@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" @@ -9464,6 +10521,17 @@ pathe@^1.1.0, pathe@^1.1.1, pathe@^1.1.2: resolved "https://registry.yarnpkg.com/pathe/-/pathe-1.1.2.tgz#6c4cb47a945692e48a1ddd6e4094d170516437ec" integrity sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ== +pbkdf2@^3.0.17: + version "3.1.2" + resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.2.tgz#dd822aa0887580e52f1a039dc3eda108efae3075" + integrity sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA== + dependencies: + create-hash "^1.1.2" + create-hmac "^1.1.4" + ripemd160 "^2.0.1" + safe-buffer "^5.0.1" + sha.js "^2.4.8" + performance-now@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" @@ -9580,17 +10648,26 @@ pkg-types@^1.0.3: mlly "^1.2.0" pathe "^1.1.0" -playwright-core@1.43.1: - version "1.43.1" - resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.43.1.tgz#0eafef9994c69c02a1a3825a4343e56c99c03b02" - integrity sha512-EI36Mto2Vrx6VF7rm708qSnesVQKbxEWvPrfA1IPY6HgczBplDx7ENtx+K2n4kJ41sLLkuGfmb0ZLSSXlDhqPg== +playwright-core@1.51.1: + version "1.51.1" + resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.51.1.tgz#d57f0393e02416f32a47cf82b27533656a8acce1" + integrity sha512-/crRMj8+j/Nq5s8QcvegseuyeZPxpQCZb6HNk3Sos3BlZyAknRjoyJPFWkpNn8v0+P3WiwqFF8P+zQo4eqiNuw== -playwright@1.43.1, playwright@^1.29.2: - version "1.43.1" - resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.43.1.tgz#8ad08984ac66c9ef3d0db035be54dd7ec9f1c7d9" - integrity sha512-V7SoH0ai2kNt1Md9E3Gwas5B9m8KR2GVvwZnAI6Pg0m3sh7UvgiYhRrhsziCmqMJNouPckiOhk8T+9bSAK0VIA== +playwright-qase-reporter@^2.0.17: + version "2.0.17" + resolved "https://registry.yarnpkg.com/playwright-qase-reporter/-/playwright-qase-reporter-2.0.17.tgz#5ed15a5b0bcc676ac6e3b267880149fb391a8079" + integrity sha512-BK2O39zQnC+BQzWZmZ7fkfNPRQbTW+Gn86flj2jbEYmhRPGHgGfDaT4EGBTsiwqHns5lTA3eAfMmgrlZNDnM7A== dependencies: - playwright-core "1.43.1" + chalk "^4.1.2" + qase-javascript-commons "~2.2.14" + uuid "^9.0.0" + +playwright@1.51.1: + version "1.51.1" + resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.51.1.tgz#ae1467ee318083968ad28d6990db59f47a55390f" + integrity sha512-kkx+MB2KQRkyxjYPc3a0wLZZoDczmppyGJIvQ43l+aZihkaVvmu/21kiyaHeHjiFxjxNNFnUncKmcGIyOojsaw== + dependencies: + playwright-core "1.51.1" optionalDependencies: fsevents "2.3.2" @@ -9662,6 +10739,11 @@ pretty-format@^29.7.0: ansi-styles "^5.0.0" react-is "^18.0.0" +process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + process-warning@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/process-warning/-/process-warning-1.0.0.tgz#980a0b25dc38cd6034181be4b7726d89066b4616" @@ -9728,6 +10810,32 @@ pure-rand@^6.0.0: resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.0.4.tgz#50b737f6a925468679bff00ad20eade53f37d5c7" integrity sha512-LA0Y9kxMYv47GIPJy6MI84fqTd2HmYZI83W/kM/SkKfDlajnZYfmXFTxkbY+xSBPkLJxltMa9hIkmdc29eguMA== +qase-javascript-commons@^2.2.19, qase-javascript-commons@~2.2.14: + version "2.2.19" + resolved "https://registry.yarnpkg.com/qase-javascript-commons/-/qase-javascript-commons-2.2.19.tgz#12adaa2ca05e34299bc95839f3d6b4d4ab7825b6" + integrity sha512-9JofC+vK6lX9AX2Xmr5hTcwgKk3BeoWEdw3osyox5ZiaOgQeS6twhqVukAjxmoPFDMNQdR/XicHLQRqGKi9KXA== + dependencies: + ajv "^8.12.0" + async-mutex "~0.5.0" + chalk "^4.1.2" + env-schema "^5.2.0" + form-data "^4.0.0" + lodash.get "^4.4.2" + lodash.merge "^4.6.2" + lodash.mergewith "^4.6.2" + mime-types "^2.1.33" + qaseio "~2.4.0" + strip-ansi "^6.0.1" + uuid "^9.0.0" + +qaseio@^2.4.3, qaseio@~2.4.0: + version "2.4.3" + resolved "https://registry.yarnpkg.com/qaseio/-/qaseio-2.4.3.tgz#678ea015b16f27d857d277b60ce17d7dd899c320" + integrity sha512-wJP9tmkZdp6YOLaxdJJ6fXoKKCme/Ec9YDnlooq6bE5Y5ihgvIPECM0eCrs3KAVOJIbqZ/S2O5dPrVrOJT5t1g== + dependencies: + axios "^1.8.2" + axios-retry "^3.5.0" + qrcode@1.5.3, qrcode@^1.5.1: version "1.5.3" resolved "https://registry.yarnpkg.com/qrcode/-/qrcode-1.5.3.tgz#03afa80912c0dccf12bc93f615a535aad1066170" @@ -9765,7 +10873,7 @@ query-string@^6.13.5: split-on-first "^1.0.0" strict-uri-encode "^2.0.0" -queue-microtask@^1.2.2: +queue-microtask@^1.2.2, queue-microtask@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== @@ -9918,6 +11026,19 @@ readable-stream@3, readable-stream@^3.0.0, readable-stream@^3.1.1, readable-stre string_decoder "^1.1.1" util-deprecate "^1.0.1" +readable-stream@^2.0.2, readable-stream@~2.3.6: + version "2.3.8" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" + integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + readable-stream@^4.0.0: version "4.5.2" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-4.5.2.tgz#9e7fc4c45099baeed934bff6eb97ba6cf2729e09" @@ -9985,6 +11106,11 @@ reef-knot@^4.2.0: "@reef-knot/wallets-list" "1.13.3" "@reef-knot/web3-react" "3.0.1" +reflect-metadata@^0.1.13: + version "0.1.14" + resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.14.tgz#24cf721fe60677146bb77eeb0e1f9dece3d65859" + integrity sha512-ZhYeb6nRaXCfhnndflDK8qI6ZQ/YcWZCISRAWICW9XYqMUwjZM9Z0DveWX/ABN01oxSHwVxKQmxeYZSsm0jh5A== + reflect.getprototypeof@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz#3ab04c32a8390b770712b7a8633972702d278859" @@ -10204,6 +11330,13 @@ rfdc@^1.3.0: resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.1.tgz#2b6d4df52dffe8bb346992a10ea9451f24373a8f" integrity sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg== +rimraf@2: + version "2.7.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" + integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== + dependencies: + glob "^7.1.3" + rimraf@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" @@ -10211,7 +11344,7 @@ rimraf@^3.0.2: dependencies: glob "^7.1.3" -ripemd160@^2.0.1: +ripemd160@^2.0.0, ripemd160@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" integrity sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA== @@ -10219,6 +11352,13 @@ ripemd160@^2.0.1: hash-base "^3.0.0" inherits "^2.0.1" +rlp@^2.2.4: + version "2.2.7" + resolved "https://registry.yarnpkg.com/rlp/-/rlp-2.2.7.tgz#33f31c4afac81124ac4b283e2bd4d9720b30beaf" + integrity sha512-d5gdPmgQ0Z+AklL2NVXr/IoSjNZFfTVvQWzL/AM2AOcSzYP2xjlb0AC8YyCLc41MSNf6P6QVtjgPdmVtzb+4lQ== + dependencies: + bn.js "^5.2.0" + rpc-websockets@^9.0.2: version "9.0.2" resolved "https://registry.yarnpkg.com/rpc-websockets/-/rpc-websockets-9.0.2.tgz#4c1568d00b8100f997379a363478f41f8f4b242c" @@ -10254,6 +11394,13 @@ rxjs@^6.4.0, rxjs@^6.6.3: dependencies: tslib "^1.9.0" +rxjs@^7.5.6: + version "7.8.2" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.2.tgz#955bc473ed8af11a002a2be52071bf475638607b" + integrity sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA== + dependencies: + tslib "^2.1.0" + rxjs@^7.8.1: version "7.8.1" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543" @@ -10271,11 +11418,16 @@ safe-array-concat@^1.1.2: has-symbols "^1.0.3" isarray "^2.0.5" -safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0: +safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@^5.2.1, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== +safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + safe-regex-test@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.3.tgz#a5b4c0f06e0ab50ea2c395c14d8371232924c377" @@ -10311,11 +11463,29 @@ schema-utils@^3.0.0: ajv "^6.12.5" ajv-keywords "^3.5.2" -scrypt-js@3.0.1: +scrypt-js@3.0.1, scrypt-js@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/scrypt-js/-/scrypt-js-3.0.1.tgz#d314a57c2aef69d1ad98a138a21fe9eafa9ee312" integrity sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA== +secp256k1@4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/secp256k1/-/secp256k1-4.0.3.tgz#c4559ecd1b8d3c1827ed2d1b94190d69ce267303" + integrity sha512-NLZVf+ROMxwtEj3Xa562qgv2BK5e2WNmXPiOdVIPLgs6lyTzMvBq0aWTYMI5XCP9jZMVKOcqZLw/Wc4vDkuxhA== + dependencies: + elliptic "^6.5.4" + node-addon-api "^2.0.0" + node-gyp-build "^4.2.0" + +secp256k1@^4.0.1: + version "4.0.4" + resolved "https://registry.yarnpkg.com/secp256k1/-/secp256k1-4.0.4.tgz#58f0bfe1830fe777d9ca1ffc7574962a8189f8ab" + integrity sha512-6JfvwvjUOn8F/jUoBY2Q1v5WY5XS+rj8qSe0v8Y4ezH4InLgTEeOOPQsRll9OV429Pvo6BCHGavIyJfr3TAhsw== + dependencies: + elliptic "^6.5.7" + node-addon-api "^5.0.0" + node-gyp-build "^4.2.0" + section-matter@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/section-matter/-/section-matter-1.0.0.tgz#e9041953506780ec01d59f292a19c7b850b84167" @@ -10324,6 +11494,11 @@ section-matter@^1.0.0: extend-shallow "^2.0.1" kind-of "^6.0.0" +seedrandom@3.0.5: + version "3.0.5" + resolved "https://registry.yarnpkg.com/seedrandom/-/seedrandom-3.0.5.tgz#54edc85c95222525b0c7a6f6b3543d8e0b3aa0a7" + integrity sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg== + "semver@2 || 3 || 4 || 5": version "5.7.2" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" @@ -10375,7 +11550,12 @@ set-function-name@^2.0.1, set-function-name@^2.0.2: functions-have-names "^1.2.3" has-property-descriptors "^1.0.2" -sha.js@^2.4.0, sha.js@^2.4.11: +setimmediate@^1.0.5, setimmediate@~1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" + integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA== + +sha.js@^2.4.0, sha.js@^2.4.11, sha.js@^2.4.8: version "2.4.11" resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ== @@ -10720,6 +11900,13 @@ string_decoder@^1.1.1, string_decoder@^1.3.0: dependencies: safe-buffer "~5.2.0" +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + stringify-entities@^3.0.1: version "3.1.0" resolved "https://registry.yarnpkg.com/stringify-entities/-/stringify-entities-3.1.0.tgz#b8d3feac256d9ffcc9fa1fefdcf3ca70576ee903" @@ -11042,6 +12229,11 @@ tr46@~0.0.3: resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== +"traverse@>=0.3.0 <0.4": + version "0.3.9" + resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.3.9.tgz#717b8f220cc0bb7b44e40514c22b2e8bbc70d8b9" + integrity sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ== + trim-newlines@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.1.tgz#260a5d962d8b752425b32f3a7db0dcacd176c144" @@ -11125,6 +12317,11 @@ tslib@1.14.1, tslib@^1.8.1, tslib@^1.9.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== +tslib@2.8.1: + version "2.8.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" + integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== + tslib@^2.0.0, tslib@^2.1.0, tslib@^2.3.1, tslib@^2.4.0: version "2.6.2" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" @@ -11286,6 +12483,13 @@ ufo@^1.3.0, ufo@^1.3.1, ufo@^1.3.2: resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.3.2.tgz#c7d719d0628a1c80c006d2240e0d169f6e3c0496" integrity sha512-o+ORpgGwaYQXgqGDwd+hkS4PuZ3QnmqMMxRuajK/a38L6fTpcE5GPIfrf+L/KemFzfUpeUQc1rRS1iDBozvnFA== +uid@2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/uid/-/uid-2.0.2.tgz#4b5782abf0f2feeefc00fa88006b2b3b7af3e3b9" + integrity sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g== + dependencies: + "@lukeed/csprng" "^1.0.0" + uint8arrays@^3.0.0, uint8arrays@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/uint8arrays/-/uint8arrays-3.1.1.tgz#2d8762acce159ccd9936057572dade9459f65ae0" @@ -11439,6 +12643,22 @@ untun@^0.1.3: consola "^3.2.3" pathe "^1.1.1" +unzipper@^0.10.11: + version "0.10.14" + resolved "https://registry.yarnpkg.com/unzipper/-/unzipper-0.10.14.tgz#d2b33c977714da0fbc0f82774ad35470a7c962b1" + integrity sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g== + dependencies: + big-integer "^1.6.17" + binary "~0.3.0" + bluebird "~3.4.1" + buffer-indexof-polyfill "~1.0.0" + duplexer2 "~0.1.4" + fstream "^1.0.12" + graceful-fs "^4.2.2" + listenercount "~1.0.1" + readable-stream "~2.3.6" + setimmediate "~1.0.4" + update-browserslist-db@^1.0.13: version "1.0.13" resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz#3c5e4f5c083661bd38ef64b6328c26ed6c8248c4" @@ -11486,6 +12706,20 @@ use-sync-external-store@1.2.0, use-sync-external-store@^1.2.0: resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a" integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA== +utf-8-validate@5.0.7: + version "5.0.7" + resolved "https://registry.yarnpkg.com/utf-8-validate/-/utf-8-validate-5.0.7.tgz#c15a19a6af1f7ad9ec7ddc425747ca28c3644922" + integrity sha512-vLt1O5Pp+flcArHGIyKEQq883nBt8nN8tVBcoL0qUXj2XT1n7p70yGIq2VK98I5FdZ1YHc0wk/koOnHjnXWk1Q== + dependencies: + node-gyp-build "^4.3.0" + +utf-8-validate@6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/utf-8-validate/-/utf-8-validate-6.0.3.tgz#7d8c936d854e86b24d1d655f138ee27d2636d777" + integrity sha512-uIuGf9TWQ/y+0Lp+KGZCMuJWc3N9BHA+l/UmHd/oUHwJJDeysyTRxNQVkbzsIWfGFbRe3OcgML/i0mvVRPOyDA== + dependencies: + node-gyp-build "^4.3.0" + utf-8-validate@^5.0.2: version "5.0.10" resolved "https://registry.yarnpkg.com/utf-8-validate/-/utf-8-validate-5.0.10.tgz#d7d10ea39318171ca982718b6b96a8d2442571a2" @@ -11493,7 +12727,7 @@ utf-8-validate@^5.0.2: dependencies: node-gyp-build "^4.3.0" -util-deprecate@^1.0.1: +util-deprecate@^1.0.1, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== @@ -11519,6 +12753,11 @@ uuid@^8.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== +uuid@^9.0.0: + version "9.0.1" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30" + integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA== + v8-compile-cache-lib@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" @@ -11572,6 +12811,20 @@ vfile@^4.0.0: unist-util-stringify-position "^2.0.0" vfile-message "^2.0.0" +viem@^2.21.40: + version "2.25.0" + resolved "https://registry.yarnpkg.com/viem/-/viem-2.25.0.tgz#53e2438512f21233be1f2850b9862e80fc512041" + integrity sha512-TtFgfQkZOfb642s8+i+h27dRhBfZV//WWOkZ9saoS1Ml8kipj9RiOiDaSmAUly1rhq9kbnYhni1xVtb195XVGA== + dependencies: + "@noble/curves" "1.8.1" + "@noble/hashes" "1.7.1" + "@scure/bip32" "1.6.2" + "@scure/bip39" "1.5.4" + abitype "1.0.8" + isows "1.0.6" + ox "0.6.9" + ws "8.18.1" + wagmi@0.12.19: version "0.12.19" resolved "https://registry.yarnpkg.com/wagmi/-/wagmi-0.12.19.tgz#5f5038330907f70c033ea51ef8a9136289567256" @@ -11781,6 +13034,21 @@ ws@7.4.6: resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== +ws@8.13.0: + version "8.13.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.13.0.tgz#9a9fb92f93cf41512a0735c8f4dd09b8a1211cd0" + integrity sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA== + +ws@8.18.0: + version "8.18.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc" + integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== + +ws@8.18.1: + version "8.18.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.1.tgz#ea131d3784e1dfdff91adb0a4a116b127515e3cb" + integrity sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w== + ws@^7.3.1, ws@^7.5.1, ws@^7.5.10: version "7.5.10" resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.10.tgz#58b5c20dc281633f6c19113f39b349bd8bd558d9" @@ -11879,6 +13147,11 @@ yocto-queue@^0.1.0: resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== +zod@^3.24.2: + version "3.24.2" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.24.2.tgz#8efa74126287c675e92f46871cfc8d15c34372b3" + integrity sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ== + zustand@^4.3.1: version "4.5.0" resolved "https://registry.yarnpkg.com/zustand/-/zustand-4.5.0.tgz#141354af56f91de378aa6c4b930032ab338f3ef0" From 1dc3184bf450df0bfd8c7592a4bcdebb9a11cc10 Mon Sep 17 00:00:00 2001 From: Aleksandr Date: Fri, 4 Apr 2025 21:42:49 +0300 Subject: [PATCH 067/418] fix: add discord-reporter --- package.json | 1 + yarn.lock | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/package.json b/package.json index 39e55889..d8049e75 100644 --- a/package.json +++ b/package.json @@ -86,6 +86,7 @@ "@commitlint/config-conventional": "^17.4.4", "@commitlint/prompt": "^17.4.4", "@lidofinance/browser-service": "1.0.0-alpha-move-br.3", + "@lidofinance/discord-reporter": "1.3.1-alpha-move-br.1", "@lidofinance/eslint-config": "^0.43.0", "@lidofinance/wallets-testing-extensions": "1.1.1-alpha-move-br.1", "@lidofinance/wallets-testing-wallets": "1.30.0-alpha-move-br.1", diff --git a/yarn.lock b/yarn.lock index 137d39fc..322fa2ef 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3051,6 +3051,14 @@ "@lidofinance/wallets-testing-wallets" "1.30.0-alpha-move-br.1" "@playwright/test" "^1.51.1" +"@lidofinance/discord-reporter@1.3.1-alpha-move-br.1": + version "1.3.1-alpha-move-br.1" + resolved "https://registry.yarnpkg.com/@lidofinance/discord-reporter/-/discord-reporter-1.3.1-alpha-move-br.1.tgz#31c4196438e29fa76991a786d8594838b76b0a32" + integrity sha512-68YrgtjibCneGk62mS08YXcRYVdVy9wY4kMi47YOgiUoCtY5YozDO6FnZ44/vTcQALZ2uT+6R43wZMuS0GM2mA== + dependencies: + "@playwright/test" "^1.51.1" + axios "^0.27.2" + "@lidofinance/eslint-config@^0.43.0": version "0.43.0" resolved "https://registry.yarnpkg.com/@lidofinance/eslint-config/-/eslint-config-0.43.0.tgz#4841f6c3a89e16520ab053a0873c462d806cd341" From 2f7ce521aff7324d9daa9a0b788e7deeb3eb8390 Mon Sep 17 00:00:00 2001 From: exromany Date: Mon, 7 Apr 2025 13:29:24 +0300 Subject: [PATCH 068/418] feat: ethseer api proxy --- .env.example | 4 + config/groups/cache.ts | 5 ++ consts/api.ts | 1 + .../attestation-rate-section/index.ts | 1 + features/monitoring/monitoring.tsx | 6 ++ global.d.ts | 3 + next.config.mjs | 3 + pages/api/ethseer-rate.ts | 42 +++++++++ types/ethseer.ts | 10 +++ utilsApi/getCurrentFrame.ts | 85 +++++++++++++++++++ utilsApi/getEthSeerRate.ts | 64 ++++++++++++++ 11 files changed, 224 insertions(+) create mode 100644 features/monitoring/attestation-rate-section/index.ts create mode 100644 pages/api/ethseer-rate.ts create mode 100644 types/ethseer.ts create mode 100644 utilsApi/getCurrentFrame.ts create mode 100644 utilsApi/getEthSeerRate.ts diff --git a/.env.example b/.env.example index d14960ca..33ab83f3 100644 --- a/.env.example +++ b/.env.example @@ -44,5 +44,9 @@ WALLETCONNECT_PROJECT_ID= # ETH Stake Widget API for IPFS mode WIDGET_API_BASE_PATH_FOR_IPFS= +# EthSeer API +ETHSEER_API_URL=https://monitoreth.io/data-api/api/eth/v1/beacon/consensus/lido/csm/participation_rate +ETHSEER_API_TOKEN= + # Survey api SURVEY_API=https://survey.testnet.fi diff --git a/config/groups/cache.ts b/config/groups/cache.ts index b71130fc..309b5861 100644 --- a/config/groups/cache.ts +++ b/config/groups/cache.ts @@ -23,6 +23,11 @@ export const CACHE_ETH_PRICE_TTL = ms('1m'); export const CACHE_ETH_PRICE_HEADERS = 'public, max-age=60, stale-if-error=1200, stale-while-revalidate=30'; +export const CACHE_ETHSEER_RATE_KEY = 'cache-ethseer-rate'; +export const CACHE_ETHSEER_RATE_TTL = ms('3m'); +export const CACHE_ETHSEER_RATE_HEADERS = + 'public, max-age=180, stale-if-error=1200, stale-while-revalidate=30'; + export const CACHE_ONE_INCH_RATE_KEY = 'oneinch-rate'; export const CACHE_ONE_INCH_RATE_TTL = ms('5m'); diff --git a/consts/api.ts b/consts/api.ts index 0c8e3eae..68fc8332 100644 --- a/consts/api.ts +++ b/consts/api.ts @@ -2,6 +2,7 @@ export const enum API_ROUTES { ETH_PRICE = 'api/eth-price', + ETHSEER_RATE = 'api/ethseer-rate', RPC = 'api/rpc', CL = 'api/cl', METRICS = 'api/metrics', diff --git a/features/monitoring/attestation-rate-section/index.ts b/features/monitoring/attestation-rate-section/index.ts new file mode 100644 index 00000000..f483ef30 --- /dev/null +++ b/features/monitoring/attestation-rate-section/index.ts @@ -0,0 +1 @@ +export * from './attestation-rate-section'; diff --git a/features/monitoring/monitoring.tsx b/features/monitoring/monitoring.tsx index 3f809169..fdda077a 100644 --- a/features/monitoring/monitoring.tsx +++ b/features/monitoring/monitoring.tsx @@ -1,9 +1,15 @@ import { FC } from 'react'; import { ExternalSection } from './external'; +import { AttestationRateSection } from './attestation-rate-section'; +import { getConfig } from 'config'; +import { CHAINS } from 'consts/chains'; + +const { defaultChain } = getConfig(); export const Monitoring: FC = () => { return ( <> + {defaultChain === CHAINS.Mainnet && } ); diff --git a/global.d.ts b/global.d.ts index b23d0dd4..2995814d 100644 --- a/global.d.ts +++ b/global.d.ts @@ -35,6 +35,9 @@ declare module 'next/config' { clApiUrls_17000: string | undefined; clApiUrls_560048: string | undefined; + ethseerApiUrl: string | undefined; + ethseerApiToken: string | undefined; + cspTrustedHosts: string | undefined; cspReportUri: string | undefined; cspReportOnly: string | undefined; diff --git a/next.config.mjs b/next.config.mjs index b6f98bcc..0d8ff4e6 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -170,6 +170,9 @@ export default withBundleAnalyzer({ clApiUrls_17000: process.env.CL_API_URLS_17000, clApiUrls_560048: process.env.CL_API_URLS_560048, + ethseerApiUrl: process.env.ETHSEER_API_URL, + ethseerApiToken: process.env.ETHSEER_API_TOKEN, + cspTrustedHosts: process.env.CSP_TRUSTED_HOSTS, cspReportUri: process.env.CSP_REPORT_URI, cspReportOnly: process.env.CSP_REPORT_ONLY, diff --git a/pages/api/ethseer-rate.ts b/pages/api/ethseer-rate.ts new file mode 100644 index 00000000..429318b2 --- /dev/null +++ b/pages/api/ethseer-rate.ts @@ -0,0 +1,42 @@ +import { + cacheControl, + wrapRequest as wrapNextRequest, +} from '@lidofinance/next-api-wrapper'; +import { Cache } from 'memory-cache'; +import { config } from 'config'; +import { API_ROUTES } from 'consts/api'; +import { defaultErrorHandler, rateLimit, responseTimeMetric } from 'utilsApi'; +import Metrics from 'utilsApi/metrics'; +import { API } from 'types'; +import { getFirstParam } from 'utils'; +import { getEthSeerRate } from 'utilsApi/getEthSeerRate'; + +const cache = new Cache(); + +// Proxy for third-party API. +const ethseerRate: API = async (req, res) => { + const id = getFirstParam(req.query['node-operator-id']); + if (!id) { + res.statusCode = 400; + throw new Error('Error: empty query param node-operator-id'); + } + + const cacheKey = `${config.CACHE_ETHSEER_RATE_KEY}_${id}`; + const cached = cache.get(cacheKey); + + if (cached) { + res.json(cached); + } else { + const response = await getEthSeerRate(id); + cache.put(cacheKey, response, config.CACHE_ETH_PRICE_TTL); + + res.json(response); + } +}; + +export default wrapNextRequest([ + rateLimit, + responseTimeMetric(Metrics.request.apiTimings, API_ROUTES.ETHSEER_RATE), + cacheControl({ headers: config.CACHE_ETH_PRICE_HEADERS }), + defaultErrorHandler, +])(ethseerRate); diff --git a/types/ethseer.ts b/types/ethseer.ts new file mode 100644 index 00000000..aa0ac248 --- /dev/null +++ b/types/ethseer.ts @@ -0,0 +1,10 @@ +export type CurrentFrame = { + startTimestamp: number; + endTimestamp: number; + numberEpochs: number; +}; + +export type RateReponse = CurrentFrame & { + operatorAttestationRate: number; + overallAttestationRate: number; +}; diff --git a/utilsApi/getCurrentFrame.ts b/utilsApi/getCurrentFrame.ts new file mode 100644 index 00000000..c33e6943 --- /dev/null +++ b/utilsApi/getCurrentFrame.ts @@ -0,0 +1,85 @@ +import { CHAINS } from '@lido-sdk/constants'; +import { iterateUrls } from '@lidofinance/rpc'; +import { config, secretConfig } from 'config'; +import { getCsmContractAddress } from 'consts/csm-constants'; +import { BigNumber } from 'ethers'; +import { HashConsensus__factory } from 'generated'; +import { Cache } from 'memory-cache'; +import { CurrentFrame } from 'types/ethseer'; +import { getStaticRpcBatchProvider } from 'utils/getStaticRpcBatchProvider'; + +const cache = new Cache(); + +const rpcUrls: Partial> = { + [CHAINS.Mainnet]: secretConfig.rpcUrls_1, + [CHAINS.Holesky]: secretConfig.rpcUrls_17000, + [CHAINS.Hoodi]: secretConfig.rpcUrls_560048, +}; + +export const getCurrentFrame = async () => { + const chainId = config.defaultChain; + const cacheKey = `${config.CACHE_ETHSEER_RATE_KEY}_frame`; + const data = cache.get(cacheKey); + if (data) { + return data; + } + + const urls = rpcUrls[chainId as CHAINS]; + if (!urls) { + throw new Error(`Error: RPC is not configured for chain ${chainId}`); + } + const currentFrame = await iterateUrls(urls, (url) => + _getCurentFrame(url, chainId), + ); + + cache.put(cacheKey, currentFrame, config.CACHE_ETHSEER_RATE_TTL); + return currentFrame; +}; + +export const _getCurentFrame = async ( + url: string, + chainId: CHAINS, +): Promise => { + const staticProvider = getStaticRpcBatchProvider(chainId, url); + + const { timestamp: latestBlockTimestamp } = + await staticProvider.getBlock('latest'); + + const hashConsensus = HashConsensus__factory.connect( + getCsmContractAddress(chainId, 'HashConsensus'), + staticProvider, + ); + + const chainConfig = await hashConsensus.getChainConfig(); + const frameConfig = await hashConsensus.getFrameConfig(); + const currentFrame = await hashConsensus.getCurrentFrame(); + + const latestSlot = BigNumber.from(latestBlockTimestamp) + .sub(chainConfig.genesisTime) + .div(chainConfig.secondsPerSlot); + + const slotsPerFrame = frameConfig.epochsPerFrame.mul( + chainConfig.slotsPerEpoch, + ); + + const startSlot = latestSlot + .sub(currentFrame.refSlot) + .div(slotsPerFrame) + .mul(slotsPerFrame) + .add(currentFrame.refSlot); + const startTimestamp = startSlot + .mul(chainConfig.secondsPerSlot) + .add(chainConfig.genesisTime) + .toNumber(); + + const numberEpochs = latestSlot + .sub(startSlot) + .div(chainConfig.slotsPerEpoch) + .toNumber(); + + return { + endTimestamp: latestBlockTimestamp, + startTimestamp, + numberEpochs, + }; +}; diff --git a/utilsApi/getEthSeerRate.ts b/utilsApi/getEthSeerRate.ts new file mode 100644 index 00000000..0ed50cbb --- /dev/null +++ b/utilsApi/getEthSeerRate.ts @@ -0,0 +1,64 @@ +import { CHAINS } from '@lido-sdk/constants'; +import ms from 'ms'; + +import { config, secretConfig } from 'config'; +import { standardFetcher } from 'utils/standardFetcher'; +import { getCurrentFrame } from './getCurrentFrame'; +import { RateReponse } from 'types/ethseer'; + +const MIN_NUMBER_EPOCHS = 9; // one hour +const MAX_NUMBER_EPOCHS = 6750; // one month + +export const getEthSeerRate = async ( + nodeOperatorId: string, +): Promise => { + const chainId = config.defaultChain; + if (chainId !== CHAINS.Mainnet) { + throw new Error(`Error: EthSeer is not support chain ${chainId}`); + } + const currentFrame = await getCurrentFrame(); + + const response = await fetchAttestationRate( + nodeOperatorId, + currentFrame.numberEpochs, + ); + + return { + ...currentFrame, + operatorAttestationRate: response.operator_participation_rate, + overallAttestationRate: response.overall_participation_rate, + }; +}; + +type EthseerApiResponse = { + operator_participation_rate: number; + overall_participation_rate: number; +}; + +const fetchAttestationRate = async ( + nodeOperatorId: string, + countEpochs: number, +) => { + const { ethseerApiUrl, ethseerApiToken } = secretConfig; + if (!ethseerApiUrl || !ethseerApiToken) { + throw new Error('Error: EthSeer API URL or token is not configured'); + } + + const numberEpochs = Math.min( + MAX_NUMBER_EPOCHS, + Math.max(countEpochs, MIN_NUMBER_EPOCHS), + ); + + const controller = new AbortController(); + const TIMEOUT = ms('5s'); + const timeoutId = setTimeout(() => controller.abort(), TIMEOUT); + + const url = `${ethseerApiUrl}?operator_number=${nodeOperatorId}&number_epochs=${numberEpochs}`; + const response = await standardFetcher(url, { + signal: controller.signal, + headers: { 'X-Api-Key': ethseerApiToken }, + }); + + clearTimeout(timeoutId); + return response; +}; From 2fd6e8af44b92121a227469525fd6672700e3c9b Mon Sep 17 00:00:00 2001 From: exromany Date: Mon, 7 Apr 2025 14:50:23 +0300 Subject: [PATCH 069/418] feat: add attestation rate section --- assets/icons/triangle-down.svg | 3 + assets/icons/triangle-up.svg | 3 + .../attestation-rate-section.tsx | 70 +++++++++++++++++++ .../attestation-rate-section/diff-badge.tsx | 20 ++++++ .../attestation-rate-section/styles.ts | 38 ++++++++++ .../attestation-rate-section/tip.tsx | 13 ++++ .../use-ethseer-api.ts | 56 +++++++++++++++ .../components/icon-tooltip/icon-tooltip.tsx | 13 ++-- shared/hooks/index.ts | 11 +-- shared/hooks/use-perf-leeway.ts | 12 ++++ shared/hooks/useRewardsFrame.ts | 2 +- 11 files changed, 231 insertions(+), 10 deletions(-) create mode 100644 assets/icons/triangle-down.svg create mode 100644 assets/icons/triangle-up.svg create mode 100644 features/monitoring/attestation-rate-section/attestation-rate-section.tsx create mode 100644 features/monitoring/attestation-rate-section/diff-badge.tsx create mode 100644 features/monitoring/attestation-rate-section/styles.ts create mode 100644 features/monitoring/attestation-rate-section/tip.tsx create mode 100644 features/monitoring/attestation-rate-section/use-ethseer-api.ts create mode 100644 shared/hooks/use-perf-leeway.ts diff --git a/assets/icons/triangle-down.svg b/assets/icons/triangle-down.svg new file mode 100644 index 00000000..d4b5489c --- /dev/null +++ b/assets/icons/triangle-down.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/triangle-up.svg b/assets/icons/triangle-up.svg new file mode 100644 index 00000000..408cd147 --- /dev/null +++ b/assets/icons/triangle-up.svg @@ -0,0 +1,3 @@ + + + diff --git a/features/monitoring/attestation-rate-section/attestation-rate-section.tsx b/features/monitoring/attestation-rate-section/attestation-rate-section.tsx new file mode 100644 index 00000000..1ada845f --- /dev/null +++ b/features/monitoring/attestation-rate-section/attestation-rate-section.tsx @@ -0,0 +1,70 @@ +import { Block, InlineLoader, Text } from '@lidofinance/lido-ui'; +import { DATA_UNAVAILABLE } from 'consts/text'; +import { FC } from 'react'; +import { IconTooltip, MatomoLink, Stack } from 'shared/components'; +import { formatDate, formatPercent } from 'utils'; +import { DiffBadge } from './diff-badge'; +import { Tip } from './tip'; +import { useEthseerApi } from './use-ethseer-api'; + +export const AttestationRateSection: FC = () => { + const { data, error } = useEthseerApi(); + + return ( + + + + + + Attestation rate + + {data && ( +
      + Monitoring frame: {formatDate(data.startTimestamp)} —{' '} + {formatDate(data.endTimestamp)} +
      + )} +
      + +
      + Data Source:{' '} + EthSeer +
      + +
      +
      + {data ? ( + <> + + + {formatPercent(data.operatorAttestationRate)}% + + + + + {data.status !== 'bad' + ? 'higher than Performance Threshold' + : 'lower than Performance Threshold'} + + + + + {data.status !== 'good' && } + + ) : error ? ( + + {DATA_UNAVAILABLE} + + ) : ( + + )} +
      +
      + ); +}; diff --git a/features/monitoring/attestation-rate-section/diff-badge.tsx b/features/monitoring/attestation-rate-section/diff-badge.tsx new file mode 100644 index 00000000..092db81d --- /dev/null +++ b/features/monitoring/attestation-rate-section/diff-badge.tsx @@ -0,0 +1,20 @@ +import { FC } from 'react'; +import { formatPercent } from 'utils'; +import { BadgeStyle } from './styles'; +import { RateStatus } from './use-ethseer-api'; + +import { ReactComponent as DownIcon } from 'assets/icons/triangle-down.svg'; +import { ReactComponent as UpIcon } from 'assets/icons/triangle-up.svg'; + +export const DiffBadge: FC<{ + values: [number, number]; + status: RateStatus; +}> = ({ values, status }) => { + const diff = values[0] - values[1]; + return ( + + {diff > 0 ? : } + {formatPercent(Math.abs(diff))}% + + ); +}; diff --git a/features/monitoring/attestation-rate-section/styles.ts b/features/monitoring/attestation-rate-section/styles.ts new file mode 100644 index 00000000..cacb4dc8 --- /dev/null +++ b/features/monitoring/attestation-rate-section/styles.ts @@ -0,0 +1,38 @@ +import styled, { css } from 'styled-components'; +import { RateStatus } from './use-ethseer-api'; + +export const TipWrapper = styled.div<{ $danger: boolean }>` + border-radius: ${({ theme }) => theme.borderRadiusesMap.lg}px; + padding: 12px; + background: ${({ $danger }) => + $danger + ? 'rgba(var(--lido-rgb-error), 0.1);' + : 'var(--lido-color-background)'}; + + color: ${({ $danger }) => + $danger ? 'var(--lido-color-error)' : 'var(--lido-color-textSecondary)'}; +`; + +const variants = { + good: css` + color: var(--lido-color-success); + background: rgba(var(--lido-rgb-success), 0.1); + `, + semi: css` + color: var(--lido-color-warning); + background: rgba(var(--lido-rgb-warning), 0.1); + `, + bad: css` + color: var(--lido-color-error); + background: rgba(var(--lido-rgb-error), 0.1); + `, +}; + +export const BadgeStyle = styled.div<{ $variant: RateStatus }>` + font-weight: 700; + font-size: ${({ theme }) => theme.fontSizesMap.xxs}px; + line-height: ${({ theme }) => theme.fontSizesMap.lg}px; + border-radius: ${({ theme }) => theme.borderRadiusesMap.xs}px; + padding: 2px 4px; + ${({ $variant }) => variants[$variant]} +`; diff --git a/features/monitoring/attestation-rate-section/tip.tsx b/features/monitoring/attestation-rate-section/tip.tsx new file mode 100644 index 00000000..47372e2b --- /dev/null +++ b/features/monitoring/attestation-rate-section/tip.tsx @@ -0,0 +1,13 @@ +import { FC } from 'react'; +import { MatomoLink } from 'shared/components'; +import { TipWrapper } from './styles'; + +export const Tip: FC<{ danger?: boolean }> = ({ danger = false }) => ( + + Check out the{' '} + + tips + {' '} + on how to improve your performance + +); diff --git a/features/monitoring/attestation-rate-section/use-ethseer-api.ts b/features/monitoring/attestation-rate-section/use-ethseer-api.ts new file mode 100644 index 00000000..d4fc5e9d --- /dev/null +++ b/features/monitoring/attestation-rate-section/use-ethseer-api.ts @@ -0,0 +1,56 @@ +import { useLidoSWR } from '@lido-sdk/react'; +import { API_ROUTES } from 'consts/api'; +import { STRATEGY_LAZY } from 'consts/swr-strategies'; +import { useNodeOperatorId } from 'providers/node-operator-provider'; +import { useMemo } from 'react'; +import { useMergeSwr, usePerfLeeway } from 'shared/hooks'; +import { NodeOperatorId } from 'types'; +import { RateReponse } from 'types/ethseer'; +import { standardFetcher } from 'utils'; + +const BP = 10_000; + +export type RateStatus = 'good' | 'semi' | 'bad'; + +const useEthSeerRate = ( + nodeOperatorId?: NodeOperatorId, + config = STRATEGY_LAZY, +) => { + const BASE_URL = typeof window === 'undefined' ? '' : window.location.origin; + const url = `${BASE_URL}/${API_ROUTES.ETHSEER_RATE}?node-operator-id=${nodeOperatorId}`; + + return useLidoSWR( + ['ethseer', nodeOperatorId], + nodeOperatorId ? async () => standardFetcher(url) : null, + config, + ); +}; + +export const useEthseerApi = () => { + const nodeOperatorId = useNodeOperatorId(); + const swrRateApi = useEthSeerRate(nodeOperatorId); + const swrPerfLeeway = usePerfLeeway(); + + const data = swrRateApi.data; + const leeway = swrPerfLeeway.data; + + return useMergeSwr( + [swrRateApi, swrPerfLeeway], + useMemo(() => { + if (!data || !leeway) return undefined; + const offset = leeway.toNumber() / BP; + const threshold = data.overallAttestationRate - offset; + return { + ...data, + threshold, + status: + data.operatorAttestationRate <= threshold + ? 'bad' + : data.operatorAttestationRate > + data.overallAttestationRate - offset / 2 + ? 'good' + : ('semi' as RateStatus), + }; + }, [data, leeway]), + ); +}; diff --git a/shared/components/icon-tooltip/icon-tooltip.tsx b/shared/components/icon-tooltip/icon-tooltip.tsx index 4df5eb58..be256b4f 100644 --- a/shared/components/icon-tooltip/icon-tooltip.tsx +++ b/shared/components/icon-tooltip/icon-tooltip.tsx @@ -1,19 +1,24 @@ import { Tooltip } from '@lidofinance/lido-ui'; -import { FC } from 'react'; +import { ComponentProps, FC } from 'react'; import { ReactComponent as InfoIcon } from 'assets/icons/info.svg'; import { ReactComponent as CalendarIcon } from 'assets/icons/info-calendar.svg'; import { IconStyle } from './style'; -type Props = { +type Props = Omit, 'title' | 'children'> & { tooltip?: string; type?: 'info' | 'calendar'; }; -export const IconTooltip: FC = ({ tooltip, type = 'info' }) => ( +export const IconTooltip: FC = ({ + tooltip, + type = 'info', + placement = 'bottomLeft', + ...rest +}) => ( <> {tooltip && ( - + {type === 'calendar' ? : } diff --git a/shared/hooks/index.ts b/shared/hooks/index.ts index 9e8f6e73..9e1c3cc1 100644 --- a/shared/hooks/index.ts +++ b/shared/hooks/index.ts @@ -11,13 +11,13 @@ export * from './use-csm-node-operators'; export * from './use-current-static-rpc-provider'; export * from './use-default-values'; export * from './use-eth-price-fallback'; -export * from './use-eth-usd-converter'; export * from './use-eth-usd'; +export * from './use-eth-usd-converter'; export * from './use-exchange-rate'; export * from './use-exit-requested-keys-from-events'; export * from './use-external-links'; -export * from './use-invites-events-fetcher'; export * from './use-invites'; +export * from './use-invites-events-fetcher'; export * from './use-keys-available'; export * from './use-keys-cl-status'; export * from './use-keys-upload-limit'; @@ -28,6 +28,7 @@ export * from './use-network-duplicates'; export * from './use-node-operators-fetcher-from-events'; export * from './use-node-operators-with-locked-bond'; export * from './use-operator-in-other-module'; +export * from './use-perf-leeway'; export * from './use-permit-or-approve'; export * from './use-permit-signature'; export * from './use-prefixed-history'; @@ -42,8 +43,6 @@ export * from './use-tx-cost-in-usd'; export * from './use-withdrawn-key-indexes-from-events'; export * from './useAsyncMemo'; export * from './useAvailableToClaim'; -export * from './useCSMQueueBatches'; -export * from './useCSMShareLimitInfo'; export * from './useCopyToClipboard'; export * from './useCsmContracts'; export * from './useCsmCurveId'; @@ -53,6 +52,8 @@ export * from './useCsmEarlyAdoption'; export * from './useCsmEarlyAdoptionKeysLimit'; export * from './useCsmKeyRemovalFee'; export * from './useCsmKeysSummary'; +export * from './useCSMQueueBatches'; +export * from './useCSMShareLimitInfo'; export * from './useCsmStatus'; export * from './useCurveInfo'; export * from './useEarlyAdoptionMember'; @@ -75,9 +76,9 @@ export * from './useNodeOperatorKeys'; export * from './useNodeOperatorLockAmount'; export * from './useNodeOperatorNextKeysBond'; export * from './useNodeOperatorRewards'; +export * from './useNodeOperatorsCount'; export * from './useNodeOperatorSummary'; export * from './useNodeOperatorUnbondedKeys'; -export * from './useNodeOperatorsCount'; export * from './useRewardsFrame'; export * from './useSearchParams'; export * from './useSessionStorage'; diff --git a/shared/hooks/use-perf-leeway.ts b/shared/hooks/use-perf-leeway.ts new file mode 100644 index 00000000..334714c5 --- /dev/null +++ b/shared/hooks/use-perf-leeway.ts @@ -0,0 +1,12 @@ +import { useContractSWR } from '@lido-sdk/react'; +import { STRATEGY_IMMUTABLE } from 'consts/swr-strategies'; +import { useCSFeeOracleRPC } from './useCsmContracts'; + +export const usePerfLeeway = (config = STRATEGY_IMMUTABLE) => { + return useContractSWR({ + contract: useCSFeeOracleRPC(), + method: 'avgPerfLeewayBP', + params: [], + config, + }); +}; diff --git a/shared/hooks/useRewardsFrame.ts b/shared/hooks/useRewardsFrame.ts index 3f629ee3..a4e1057f 100644 --- a/shared/hooks/useRewardsFrame.ts +++ b/shared/hooks/useRewardsFrame.ts @@ -37,7 +37,7 @@ export const useRewardsFrame = () => { const currentFrame = useCurrentFrame(); return useMergeSwr( - [chainConfig, currentFrame], + [chainConfig, frameConfig, currentFrame], useMemo(() => { if (!chainConfig.data || !frameConfig.data || !currentFrame.data) return undefined; From 77c228b0f7e69eaef22f0c2c55423d51eccf8d2c Mon Sep 17 00:00:00 2001 From: exromany Date: Mon, 7 Apr 2025 17:41:47 +0300 Subject: [PATCH 070/418] chore: add FAQ to monitoring page --- faq/holesky/monitoring-1.md | 9 +++++++++ faq/holesky/monitoring-2.md | 7 +++++++ faq/hoodi/monitoring-1.md | 9 +++++++++ faq/hoodi/monitoring-2.md | 7 +++++++ faq/mainnet/monitoring-1.md | 9 +++++++++ faq/mainnet/monitoring-2.md | 7 +++++++ features/monitoring/monitoring.tsx | 8 ++++++-- lib/getFaq.ts | 3 +++ pages/monitoring.tsx | 3 ++- 9 files changed, 59 insertions(+), 3 deletions(-) create mode 100644 faq/holesky/monitoring-1.md create mode 100644 faq/holesky/monitoring-2.md create mode 100644 faq/hoodi/monitoring-1.md create mode 100644 faq/hoodi/monitoring-2.md create mode 100644 faq/mainnet/monitoring-1.md create mode 100644 faq/mainnet/monitoring-2.md diff --git a/faq/holesky/monitoring-1.md b/faq/holesky/monitoring-1.md new file mode 100644 index 00000000..e5dcc4e4 --- /dev/null +++ b/faq/holesky/monitoring-1.md @@ -0,0 +1,9 @@ +--- +title: What is the performance threshold? +--- + +The performance threshold is a relative variable determined as follows: + +`CSM Performance threshold (%) = Average validator performance across the whole Ethereum network (%) - 5%` + +A performance threshold is utilized to determine the allocation of the actual Node Operator rewards. Validators with performance above the threshold are included in the allocation pool, while the rest are not. diff --git a/faq/holesky/monitoring-2.md b/faq/holesky/monitoring-2.md new file mode 100644 index 00000000..e7f2dcb7 --- /dev/null +++ b/faq/holesky/monitoring-2.md @@ -0,0 +1,7 @@ +--- +title: How does the CSM Performance Oracle work? +--- + +The Performance Oracle uses the successful attestation rate `successfulAttestations / totalAssignedAttestations` (where `successfulAttestation` stands for the attestation that has been included in the beacon block no matter the inclusion distance) as a proxy for the overall performance of a validator. The Performance Oracle compares the performance of each validator with the performance threshold to determine whether the validator should or should not be included in the rewards distribution during the frame. + +The frame for the Performance Oracle report is set to 7 days. The Performance Oracle creates a [Merkle tree](https://en.wikipedia.org/wiki/Merkle_tree) with the allocation of the Node Operator rewards and delivers the root on-chain. To make the original tree available to users, it is published on [IPFS](https://ipfs.tech/) and [GitHub](https://github.com/lidofinance/csm-rewards). diff --git a/faq/hoodi/monitoring-1.md b/faq/hoodi/monitoring-1.md new file mode 100644 index 00000000..e5dcc4e4 --- /dev/null +++ b/faq/hoodi/monitoring-1.md @@ -0,0 +1,9 @@ +--- +title: What is the performance threshold? +--- + +The performance threshold is a relative variable determined as follows: + +`CSM Performance threshold (%) = Average validator performance across the whole Ethereum network (%) - 5%` + +A performance threshold is utilized to determine the allocation of the actual Node Operator rewards. Validators with performance above the threshold are included in the allocation pool, while the rest are not. diff --git a/faq/hoodi/monitoring-2.md b/faq/hoodi/monitoring-2.md new file mode 100644 index 00000000..eb06ee53 --- /dev/null +++ b/faq/hoodi/monitoring-2.md @@ -0,0 +1,7 @@ +--- +title: How does the CSM Performance Oracle work? +--- + +The Performance Oracle uses the successful attestation rate `successfulAttestations / totalAssignedAttestations` (where `successfulAttestation` stands for the attestation that has been included in the beacon block no matter the inclusion distance) as a proxy for the overall performance of a validator. The Performance Oracle compares the performance of each validator with the performance threshold to determine whether the validator should or should not be included in the rewards distribution during the frame. + +The frame for the Performance Oracle report is set to 1 days. The Performance Oracle creates a [Merkle tree](https://en.wikipedia.org/wiki/Merkle_tree) with the allocation of the Node Operator rewards and delivers the root on-chain. To make the original tree available to users, it is published on [IPFS](https://ipfs.tech/) and [GitHub](https://github.com/lidofinance/csm-rewards). diff --git a/faq/mainnet/monitoring-1.md b/faq/mainnet/monitoring-1.md new file mode 100644 index 00000000..e5dcc4e4 --- /dev/null +++ b/faq/mainnet/monitoring-1.md @@ -0,0 +1,9 @@ +--- +title: What is the performance threshold? +--- + +The performance threshold is a relative variable determined as follows: + +`CSM Performance threshold (%) = Average validator performance across the whole Ethereum network (%) - 5%` + +A performance threshold is utilized to determine the allocation of the actual Node Operator rewards. Validators with performance above the threshold are included in the allocation pool, while the rest are not. diff --git a/faq/mainnet/monitoring-2.md b/faq/mainnet/monitoring-2.md new file mode 100644 index 00000000..7fabe1fe --- /dev/null +++ b/faq/mainnet/monitoring-2.md @@ -0,0 +1,7 @@ +--- +title: How does the CSM Performance Oracle work? +--- + +The Performance Oracle uses the successful attestation rate `successfulAttestations / totalAssignedAttestations` (where `successfulAttestation` stands for the attestation that has been included in the beacon block no matter the inclusion distance) as a proxy for the overall performance of a validator. The Performance Oracle compares the performance of each validator with the performance threshold to determine whether the validator should or should not be included in the rewards distribution during the frame. + +The frame for the Performance Oracle report is set to 28 days. The Performance Oracle creates a [Merkle tree](https://en.wikipedia.org/wiki/Merkle_tree) with the allocation of the Node Operator rewards and delivers the root on-chain. To make the original tree available to users, it is published on [IPFS](https://ipfs.tech/) and [GitHub](https://github.com/lidofinance/csm-rewards). diff --git a/features/monitoring/monitoring.tsx b/features/monitoring/monitoring.tsx index fdda077a..dd20ce84 100644 --- a/features/monitoring/monitoring.tsx +++ b/features/monitoring/monitoring.tsx @@ -3,14 +3,18 @@ import { ExternalSection } from './external'; import { AttestationRateSection } from './attestation-rate-section'; import { getConfig } from 'config'; import { CHAINS } from 'consts/chains'; +import { Faq, NoSSRWrapper } from 'shared/components'; const { defaultChain } = getConfig(); export const Monitoring: FC = () => { return ( <> - {defaultChain === CHAINS.Mainnet && } - + + {defaultChain === CHAINS.Mainnet && } + + + ); }; diff --git a/lib/getFaq.ts b/lib/getFaq.ts index ec8a3bb9..41fe63f9 100644 --- a/lib/getFaq.ts +++ b/lib/getFaq.ts @@ -84,3 +84,6 @@ export const getFaqLocked = () => export const getFaqRoles = () => readFaqFiles(['roles-1', 'roles-2', 'roles-3', 'roles-4', 'roles-5']); + +export const getFaqMonitoring = () => + readFaqFiles(['monitoring-1', 'monitoring-2']); diff --git a/pages/monitoring.tsx b/pages/monitoring.tsx index 23344b55..8e11f5ed 100644 --- a/pages/monitoring.tsx +++ b/pages/monitoring.tsx @@ -1,5 +1,6 @@ import { PATH } from 'consts/urls'; import { MonitoringPage } from 'features/monitoring'; +import { getFaqMonitoring } from 'lib/getFaq'; import { getProps } from 'lib/getProps'; import { Gate, GateLoaded, Navigate } from 'shared/navigate'; @@ -13,4 +14,4 @@ const Page = () => ( export default Page; -export const getServerSideProps = getProps(); +export const getServerSideProps = getProps(getFaqMonitoring); From d03d1ba4c4e386a1e721948fca613e06a82c51a5 Mon Sep 17 00:00:00 2001 From: exromany Date: Mon, 7 Apr 2025 17:45:28 +0300 Subject: [PATCH 071/418] chore: hide attestation rate if NO has no deposited keys --- .../attestation-rate-section.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/features/monitoring/attestation-rate-section/attestation-rate-section.tsx b/features/monitoring/attestation-rate-section/attestation-rate-section.tsx index 1ada845f..7d30407e 100644 --- a/features/monitoring/attestation-rate-section/attestation-rate-section.tsx +++ b/features/monitoring/attestation-rate-section/attestation-rate-section.tsx @@ -6,10 +6,18 @@ import { formatDate, formatPercent } from 'utils'; import { DiffBadge } from './diff-badge'; import { Tip } from './tip'; import { useEthseerApi } from './use-ethseer-api'; +import { useNodeOperatorId } from 'providers/node-operator-provider'; +import { useNodeOperatorInfo } from 'shared/hooks'; export const AttestationRateSection: FC = () => { + const id = useNodeOperatorId(); + const { data: info } = useNodeOperatorInfo(id); const { data, error } = useEthseerApi(); + const showThisSection = data || (info?.totalDepositedKeys ?? 0) > 0; + + if (!showThisSection) return null; + return ( @@ -58,7 +66,7 @@ export const AttestationRateSection: FC = () => { {data.status !== 'good' && } ) : error ? ( - + {DATA_UNAVAILABLE} ) : ( From bb86258687d29ad35354fecc937933b0dada9ade Mon Sep 17 00:00:00 2001 From: exromany Date: Tue, 8 Apr 2025 09:08:33 +0300 Subject: [PATCH 072/418] fix: calculate external dashboards links --- consts/external-links.ts | 7 ++++--- .../stacter-pack-section/required-bond-amount.tsx | 15 ++++++++------- lib/getFaq.ts | 1 - shared/hooks/use-external-links.ts | 6 +++--- 4 files changed, 15 insertions(+), 14 deletions(-) diff --git a/consts/external-links.ts b/consts/external-links.ts index 9b502725..d7c9fa5d 100644 --- a/consts/external-links.ts +++ b/consts/external-links.ts @@ -30,7 +30,7 @@ type ExternalLinksConstants = { operatorsWidget: string; beaconchainDashboard: string; ratedExplorer: string; - ethseerDashboard?: string; + ethseerDashboard: string; subscribeEvents: string; keysApi: string; surveyApi: string; @@ -76,12 +76,12 @@ export const EXTERNAL_LINKS_BY_NETWORK: Record = operatorsWidget: 'https://operators-holesky.testnet.fi', beaconchain: 'https://holesky.beaconcha.in', beaconchainDashboard: 'https://v2-beta-holesky.beaconcha.in/dashboard', - ratedExplorer: 'https://explorer.rated.network', + ratedExplorer: '', + ethseerDashboard: 'https://ethseer.io', subscribeEvents: 'https://docs.lido.fi/staking-modules/csm/guides/events', keysApi: 'https://keys-api-holesky.testnet.fi', surveyApi: '', }, - // FIXME: links [CHAINS.Hoodi]: { earlyAdoptionTree: '', rewardsTree: '', @@ -95,6 +95,7 @@ export const EXTERNAL_LINKS_BY_NETWORK: Record = beaconchain: 'https://hoodi.beaconcha.in', beaconchainDashboard: 'https://v2-beta-hoodi.beaconcha.in/dashboard', ratedExplorer: '', + ethseerDashboard: '', subscribeEvents: 'https://docs.lido.fi/staking-modules/csm/guides/events', keysApi: 'https://keys-api-hoodi.testnet.fi', surveyApi: 'https://csm-surveys-api-testnet.up.railway.app', 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 df705971..3a112741 100644 --- a/features/starter-pack/stacter-pack-section/required-bond-amount.tsx +++ b/features/starter-pack/stacter-pack-section/required-bond-amount.tsx @@ -1,6 +1,6 @@ +import { CHAINS } from '@lido-sdk/constants'; import { Box, InlineLoader } from '@lidofinance/lido-ui'; import { getConfig } from 'config'; -import { CHAINS } from 'consts/chains'; import { FC } from 'react'; import { FormatToken } from 'shared/formatters'; import { useCsmCurveId, useCsmEarlyAdoption, useCurveInfo } from 'shared/hooks'; @@ -8,7 +8,6 @@ import { useCsmCurveId, useCsmEarlyAdoption, useCurveInfo } from 'shared/hooks'; const { defaultChain } = getConfig(); export const RequiredBondAmount: FC = () => { - const isMainnet = defaultChain === CHAINS.Mainnet; const { data: ea } = useCsmEarlyAdoption(); const { data: curveId, initialLoading: curveLoading } = useCsmCurveId( !!ea?.proof || true, @@ -17,6 +16,12 @@ export const RequiredBondAmount: FC = () => { useCurveInfo(curveId); const amount = curveInfo?.points[0]; + const chainName = CHAINS[defaultChain]; + const isTestnet = defaultChain !== CHAINS.Mainnet; + const symbol = [isTestnet ? chainName : null, 'ETH'] + .filter(Boolean) + .join(' '); + return ( <> {curveLoading || curveInfoLoading ? ( @@ -24,11 +29,7 @@ export const RequiredBondAmount: FC = () => { ) : ( - + )} ); diff --git a/lib/getFaq.ts b/lib/getFaq.ts index 41fe63f9..afebab7c 100644 --- a/lib/getFaq.ts +++ b/lib/getFaq.ts @@ -1,4 +1,3 @@ -/* eslint-disable no-console */ import matter from 'gray-matter'; import remark from 'remark'; import html from 'remark-html'; diff --git a/shared/hooks/use-external-links.ts b/shared/hooks/use-external-links.ts index 5d13f381..59e9eec4 100644 --- a/shared/hooks/use-external-links.ts +++ b/shared/hooks/use-external-links.ts @@ -4,7 +4,7 @@ import { useNodeOperatorId } from 'providers/node-operator-provider'; import { useKeysWithStatus } from './use-keys-with-status'; import { ACTIVE_STATUS_ORDER, useSortedKeys } from './use-sorted-keys'; import { getConfig } from 'config'; -import { CHAINS } from 'consts/chains'; +import { CHAINS } from '@lido-sdk/constants'; const DASHBOARD_KEYS_LIMIT = 20; @@ -42,14 +42,14 @@ export const useOperatorPortalLink = () => { export const useRatedLink = () => { const nodeOperatorId = useNodeOperatorId(); - const network = defaultChain === CHAINS.Mainnet ? 'mainnet' : 'hoodi'; + const network = CHAINS[defaultChain].toLowerCase(); if (!links.ratedExplorer) return null; return `${links.ratedExplorer}/o/CSM%20Operator%20${nodeOperatorId}%20-%20Lido%20Community%20Staking%20Module?network=${network}`; }; export const useEthSeerLink = () => { const nodeOperatorId = useNodeOperatorId(); - const network = defaultChain === CHAINS.Mainnet ? 'mainnet' : 'hoodi'; + const network = CHAINS[defaultChain].toLowerCase(); if (!links.ethseerDashboard) return null; return `${links.ethseerDashboard}/entity/csm_operator${nodeOperatorId}_lido?network=${network}`; }; From 20e5a294121442f3ad31b9cd668ca82cde9a44e2 Mon Sep 17 00:00:00 2001 From: exromany Date: Tue, 8 Apr 2025 10:52:46 +0300 Subject: [PATCH 073/418] chore: small node-operator button --- shared/node-operator/button/styles.tsx | 7 +++++++ shared/node-operator/descriptor/descriptor-id.tsx | 9 +++++++-- shared/node-operator/descriptor/styles.tsx | 10 ++++++---- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/shared/node-operator/button/styles.tsx b/shared/node-operator/button/styles.tsx index 48e8aac0..09456df2 100644 --- a/shared/node-operator/button/styles.tsx +++ b/shared/node-operator/button/styles.tsx @@ -1,5 +1,6 @@ import { Button, ButtonProps } from '@lidofinance/lido-ui'; import styled from 'styled-components'; +import { DescriptorText } from '../descriptor/styles'; export const ButtonStyle = styled((props: ButtonProps) => ( - + + + ); diff --git a/features/unlock-bond/unlock-bond-form/controls/info.tsx b/features/unlock-bond/unlock-bond-form/controls/info.tsx index eb4d4be5..10b1263c 100644 --- a/features/unlock-bond/unlock-bond-form/controls/info.tsx +++ b/features/unlock-bond/unlock-bond-form/controls/info.tsx @@ -18,7 +18,7 @@ export const Info: FC = () => { token={TOKENS.ETH} /> {lockedBond?.gt(0) && ( -
      +

      EL reward stealing {' '} @@ -46,7 +46,7 @@ export const Info: FC = () => { to avoid the further penalties.

    - +

    )} diff --git a/features/view-keys/view-keys-section/keys-table.tsx b/features/view-keys/view-keys-section/keys-table.tsx index c285114d..49b8ec78 100644 --- a/features/view-keys/view-keys-section/keys-table.tsx +++ b/features/view-keys/view-keys-section/keys-table.tsx @@ -1,4 +1,4 @@ -import { Tbody, Td, Th, Thead, Tr } from '@lidofinance/lido-ui'; +import { Tbody, Td, Text, Th, Thead, Tr } from '@lidofinance/lido-ui'; import { FC } from 'react'; import { Address, @@ -21,6 +21,7 @@ export const KeysTable: FC = ({ keys }) => { + # Key Status Comment @@ -29,6 +30,11 @@ export const KeysTable: FC = ({ keys }) => { {sortedKeys?.map(({ key, index, statuses }) => ( + + + {index + 1} + +
    { @@ -9,8 +9,10 @@ export const ViewKeys = () => { return ( <> - - + + + + diff --git a/providers/inpage-navigation.tsx b/providers/inpage-navigation.tsx index 5068d5b1..345111f5 100644 --- a/providers/inpage-navigation.tsx +++ b/providers/inpage-navigation.tsx @@ -7,22 +7,17 @@ import { useMemo, useCallback, useEffect, - useLayoutEffect, } from 'react'; import invariant from 'tiny-invariant'; import { useRouter } from 'next/router'; import { config } from 'config'; -import { lockScroll, saveScrollPosition, unlockScroll } from 'utils'; -import { debounce } from 'lodash'; export type InpageNavigationContextValue = { hashNav: string; navigateInpageAnchor: (e: React.MouseEvent) => void; resetInpageAnchor: () => void; resetSpecificAnchor: (hash: string) => void; - expanded: boolean; - toggleExpanded: () => void; }; const InpageNavigationContext = @@ -82,40 +77,14 @@ export const InpageNavigationProvider: FC = ({ [resetInpageAnchor, hashNav], ); - const [expanded, setExpanded] = useState(false); - const toggleExpanded = useCallback(() => setExpanded((prev) => !prev), []); - - useLayoutEffect(() => { - if (!expanded) return; - lockScroll(); - return unlockScroll; - }, [expanded]); - - useEffect(() => { - const handleScroll = debounce(saveScrollPosition, 10); - window.addEventListener('scroll', handleScroll, { passive: true }); - return () => { - window.removeEventListener('scroll', handleScroll); - }; - }, []); - const value = useMemo( () => ({ hashNav, navigateInpageAnchor, resetInpageAnchor, resetSpecificAnchor, - expanded, - toggleExpanded, }), - [ - expanded, - hashNav, - navigateInpageAnchor, - resetInpageAnchor, - resetSpecificAnchor, - toggleExpanded, - ], + [hashNav, navigateInpageAnchor, resetInpageAnchor, resetSpecificAnchor], ); return ( diff --git a/shared/alerts/components/alert-container.tsx b/shared/alerts/components/alert-container.tsx index b13db2ff..3ac61dec 100644 --- a/shared/alerts/components/alert-container.tsx +++ b/shared/alerts/components/alert-container.tsx @@ -4,30 +4,26 @@ import styled from 'styled-components'; import { useAlertActions } from '../alert-provider'; const AlertContainerStyled = styled.div` + position: absolute; + top: 100%; + right: 32px; + width: 300px; + display: flex; flex-direction: column; gap: ${({ theme }) => theme.spaceMap.md}px; - margin: 0 auto; + @media screen and (max-width: 1286px) { + position: static; + width: initial; + max-width: 590px; /* like Main */ + margin: 0 auto; + padding: 0 20px; + } :not(:empty) { margin-top: 20px; } - - grid-area: none; - position: absolute; - right: 32px; - top: 80px; - width: calc(50% - 350px); - max-width: 300px; - grid-area: none; - - ${({ theme }) => theme.mediaQueries.xl} { - grid-area: alerts; - position: static; - width: 100%; - max-width: var(--layout-main-width); - } `; export const AlertContainer: FC = () => { diff --git a/shared/components/address/address.tsx b/shared/components/address/address.tsx index d7f1a064..5a04b040 100644 --- a/shared/components/address/address.tsx +++ b/shared/components/address/address.tsx @@ -1,64 +1,38 @@ -import { - AddressProps, - Address as AddressComponent, - Identicon, - Text, - Tooltip, -} from '@lidofinance/lido-ui'; -import { ComponentProps, FC, ReactNode } from 'react'; +import { AddressProps, Identicon, Tooltip } from '@lidofinance/lido-ui'; +import { FC, ReactNode } from 'react'; import { EtherscanAddressLink } from '../external-icon-link'; -import { AddressContainerStyle } from './styles'; +import { AddressStyle, AddressContainerStyle } from './styles'; type Props = { showIcon?: boolean; - big?: boolean; + bold?: boolean; link?: ReactNode; -} & Pick, 'weight' | 'size' | 'color'> & - Partial; +} & Partial; export const Address: FC = ({ address = '', symbols = 6, showIcon = false, - big = false, - weight, - size = 'xs', - color, + bold = false, link, -}) => { - const component = ( - - - - ); - return ( - <> - {address && ( - - {showIcon && } - {symbols === 0 ? ( - component - ) : ( - - {component} - - )} - {link ?? } - - )} - - ); -}; +}) => ( + <> + {address && ( + + {showIcon && } + {symbols === 0 ? ( + + ) : ( + + + + )} + {link ?? } + + )} + +); diff --git a/shared/components/address/styles.tsx b/shared/components/address/styles.tsx index 22e4dcce..7f62a0c3 100644 --- a/shared/components/address/styles.tsx +++ b/shared/components/address/styles.tsx @@ -1,25 +1,30 @@ +import { Address } from '@lidofinance/lido-ui'; import styled, { css } from 'styled-components'; import { LinkStyled } from '../matomo-link/styles'; import { StackStyle } from '../stack/style'; +export const AddressStyle = styled(Address)<{ $bold?: boolean }>` + ${({ $bold }) => + $bold && + css` + font-weight: bold; + `} +`; + export const AddressContainerStyle = styled(StackStyle).attrs({ $gap: 'xs', $align: 'center', -})<{ $big?: boolean }>` +})<{ $bold?: boolean }>` display: inline-flex; - > span > span { - font-weight: inherit; - } - ${LinkStyled} { svg { display: inline-flex; flex-shrink: 0; } - ${({ $big }) => - !$big && + ${({ $bold }) => + !$bold && css` &, &:visited { diff --git a/shared/components/inverse-theme-provider/inverse-theme-provider.tsx b/shared/components/inverse-theme-provider/inverse-theme-provider.tsx index 2825f0c4..b1f34cda 100644 --- a/shared/components/inverse-theme-provider/inverse-theme-provider.tsx +++ b/shared/components/inverse-theme-provider/inverse-theme-provider.tsx @@ -1,7 +1,6 @@ import { themeDark, themeLight, - ThemeName, ThemeProvider, useThemeToggle, } from '@lidofinance/lido-ui'; @@ -10,9 +9,7 @@ import { FC, PropsWithChildren } from 'react'; export const InverseThemeProvider: FC = ({ children }) => { const { themeName } = useThemeToggle(); return ( - + {children} ); diff --git a/shared/components/logos/logos.tsx b/shared/components/logos/logos.tsx index 543367ed..00ff2bb4 100644 --- a/shared/components/logos/logos.tsx +++ b/shared/components/logos/logos.tsx @@ -1,4 +1,5 @@ -import { LidoLogo, Link } from '@lidofinance/lido-ui'; +import { LidoLogo } from '@lidofinance/lido-ui'; +import Link from 'next/link'; import { FC } from 'react'; import { LogoLidoStyle } from './styles'; @@ -6,7 +7,7 @@ import { LogoLidoStyle } from './styles'; export const LogoLido: FC = () => ( - + ); diff --git a/shared/components/logos/styles.tsx b/shared/components/logos/styles.tsx index 4092930b..dbbd3672 100644 --- a/shared/components/logos/styles.tsx +++ b/shared/components/logos/styles.tsx @@ -8,12 +8,12 @@ export const LogoLidoStyle = styled.div` flex-shrink: 0; cursor: pointer; - ${({ theme }) => theme.mediaQueries.lg} { + ${({ theme }) => theme.mediaQueries.md} { width: 14px; justify-content: flex-start; } - span { - display: block; + ${({ theme }) => theme.mediaQueries.lg} { + align-self: start; } `; diff --git a/shared/components/status-chip/style.ts b/shared/components/status-chip/style.ts index d36c0786..b78d5dee 100644 --- a/shared/components/status-chip/style.ts +++ b/shared/components/status-chip/style.ts @@ -24,7 +24,6 @@ export const StatusStyle = styled.div<{ $variant?: Variants }>` width: fit-content; padding: 4px 12px; text-align: center; - white-space: nowrap; border-radius: ${({ theme }) => theme.borderRadiusesMap.xl}px; background: color-mix(in srgb, currentColor 15%, transparent); diff --git a/shared/hooks/use-show-rule.ts b/shared/hooks/use-show-rule.ts index b858a03a..2f259cce 100644 --- a/shared/hooks/use-show-rule.ts +++ b/shared/hooks/use-show-rule.ts @@ -37,9 +37,9 @@ export const useShowRule = () => { case 'IS_CONNECTED_WALLET': return isConnectedWallet; case 'NOT_NODE_OPERATOR': - return isConnectedWallet && !nodeOperator; + return !nodeOperator; case 'IS_NODE_OPERATOR': - return !!nodeOperator && isConnectedWallet; + return !!nodeOperator; case 'CAN_CREATE': return !!canCreateNO; case 'HAS_MANAGER_ROLE': diff --git a/shared/layout/footer/footer.tsx b/shared/layout/footer/footer.tsx index f4576f85..3cf3811f 100644 --- a/shared/layout/footer/footer.tsx +++ b/shared/layout/footer/footer.tsx @@ -3,7 +3,13 @@ import { FC } from 'react'; import { getExternalLinks } from 'consts/external-links'; import { LogoLido, Stack } from 'shared/components'; -import { FooterLink, FooterStyle, LinkDivider, Version } from './styles'; +import { + FooterDivider, + FooterLink, + FooterStyle, + LinkDivider, + Version, +} from './styles'; const getVersionInfo = () => { const { version, branch } = buildInfo; @@ -38,7 +44,7 @@ const { feedbackForm } = getExternalLinks(); // TODO: matomo events export const Footer: FC = () => { return ( - + @@ -52,6 +58,7 @@ export const Footer: FC = () => { Feedback form {label} + ); }; diff --git a/shared/layout/footer/styles.tsx b/shared/layout/footer/styles.tsx index 00d7c000..1fa8b9c3 100644 --- a/shared/layout/footer/styles.tsx +++ b/shared/layout/footer/styles.tsx @@ -1,10 +1,9 @@ -import { Link } from '@lidofinance/lido-ui'; +import { Container, Link } from '@lidofinance/lido-ui'; import styled from 'styled-components'; import { NAV_MOBILE_MEDIA } from 'styles/constants'; -export const FooterStyle = styled.footer` - grid-area: footer; +export const FooterStyle = styled(Container)` position: relative; box-sizing: border-box; color: var(--lido-color-text); @@ -13,23 +12,14 @@ export const FooterStyle = styled.footer` flex-wrap: wrap; column-gap: 32px; - justify-content: center; - align-self: center; - margin-block: 24px; + width: 100%; + max-width: 1424px; + padding: 24px 32px; - &::before { - content: ''; - position: absolute; - top: -23px; - left: 0; - width: 100%; - height: 1px; - background: var(--lido-color-popupMenuItemBgActiveHover); - opacity: 0.12; - - ${NAV_MOBILE_MEDIA} { - display: none; - } + ${NAV_MOBILE_MEDIA} { + margin-bottom: 60px; + padding: 20px 18px; + justify-content: center; } `; @@ -70,6 +60,20 @@ export const LinkDivider = styled.div` margin: 2px 16px; `; +export const FooterDivider = styled.div` + position: absolute; + top: 0; + left: 32px; + width: calc(100% - 64px); + height: 1px; + background: var(--lido-color-popupMenuItemBgActiveHover); + opacity: 0.12; + + ${NAV_MOBILE_MEDIA} { + display: none; + } +`; + export const Version = styled(FooterLink)` margin-left: auto; padding: 2px 5px; diff --git a/shared/layout/header/components/header-burger.tsx b/shared/layout/header/components/header-burger.tsx deleted file mode 100644 index cd1de1d2..00000000 --- a/shared/layout/header/components/header-burger.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import { Button } from '@lidofinance/lido-ui'; -import { useInpageNavigation } from 'providers/inpage-navigation'; -import { FC } from 'react'; -import styled from 'styled-components'; -import { NAV_MOBILE_MEDIA } from 'styles/constants'; - -export const HeaderBurger: FC = () => { - const { expanded, toggleExpanded } = useInpageNavigation(); - return ( - - - - - - ); -}; - -export const BurgerLine = styled.div` - width: 18px; - height: 2px; - border-radius: 2px; - margin-block: 4px; - - background-color: var(--lido-color-text); - transition: - transform ease ${({ theme }) => theme.duration.norm}, - opacity ease ${({ theme }) => theme.duration.norm}; -`; - -export const BurgerButtonStyle = styled(Button).attrs({ - size: 'xs', - variant: 'text', - color: 'secondary', -})` - display: none; - - width: 44px; - height: 44px; - min-width: unset; - align-items: center; - justify-content: center; - flex-shrink: 1; - padding: 0; - - ${NAV_MOBILE_MEDIA} { - display: flex; - } - - &[aria-expanded='true'] ${BurgerLine} { - :nth-child(1) { - transform: translateY(6px) rotate(-45deg); - } - :nth-child(2) { - opacity: 0; - } - :nth-child(3) { - transform: translateY(-6px) rotate(45deg); - } - } -`; diff --git a/shared/layout/header/components/header-theme.tsx b/shared/layout/header/components/header-theme.tsx index b90ad0d5..b4aabe4a 100644 --- a/shared/layout/header/components/header-theme.tsx +++ b/shared/layout/header/components/header-theme.tsx @@ -2,18 +2,12 @@ import { useRouter } from 'next/router'; import { FC } from 'react'; import { ThemeTogglerStyle } from '../styles'; -const HeaderTheme: FC<{ showAlways?: boolean }> = ({ showAlways }) => { +const HeaderTheme: FC = () => { const router = useRouter(); const queryTheme = router?.query?.theme; - return ( - <> - {!queryTheme && ( - - )} - - ); + return <>{!queryTheme && }; }; export default HeaderTheme; diff --git a/shared/layout/header/components/logos.tsx b/shared/layout/header/components/logos.tsx deleted file mode 100644 index 01ea3a01..00000000 --- a/shared/layout/header/components/logos.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { Link, Text } from '@lidofinance/lido-ui'; -import { FC } from 'react'; -import { LogoLido } from 'shared/components'; -import { LogoDivider, LogosStyle } from '../styles'; - -export const Logos: FC = () => ( - - - - - CSM - - -); diff --git a/shared/layout/header/components/navigation/navigation.tsx b/shared/layout/header/components/navigation/navigation.tsx new file mode 100644 index 00000000..6429deb6 --- /dev/null +++ b/shared/layout/header/components/navigation/navigation.tsx @@ -0,0 +1,33 @@ +import { FC, memo } from 'react'; +import { Stack } from 'shared/components'; +import { useRouterPath } from 'shared/hooks'; +import { LocalLink } from 'shared/navigate'; +import { getIsActivePath } from 'utils/path'; +import { Nav, NavLink } from './styles'; +import { useNavItems } from './use-nav-items'; + +export const Navigation: FC = memo(() => { + const routes = useNavItems(); + + const pathname = useRouterPath(); + + return ( + + ); +}); diff --git a/shared/layout/header/components/navigation/styles.tsx b/shared/layout/header/components/navigation/styles.tsx new file mode 100644 index 00000000..7adab00c --- /dev/null +++ b/shared/layout/header/components/navigation/styles.tsx @@ -0,0 +1,92 @@ +import { CounterStyle } from 'shared/components/counter/styles'; +import styled, { css } from 'styled-components'; + +import { NAV_MOBILE_HEIGHT, NAV_MOBILE_MEDIA } from 'styles/constants'; + +export const desktopCss = css` + margin: 0 46px; + display: flex; + gap: 32px; + + svg { + flex-shrink: 0; + } +`; + +const mobileCss = css` + margin: 0; + position: fixed; + bottom: 0; + left: 0; + right: 0; + padding: 8px; + background-color: var(--lido-color-foreground); + display: flex; + gap: 32px; + justify-content: space-around; + align-items: center; + border-top: 1px solid var(--lido-color-border); + height: ${NAV_MOBILE_HEIGHT}px; +`; + +export const Nav = styled.div` + ${desktopCss} + // mobile kicks in on a bit higher width for nav + ${NAV_MOBILE_MEDIA} { + ${mobileCss} + } + align-items: center; + z-index: 6; +`; + +// Not wrapping inside in IPFS mode +// Also avoid problems with migrate to Next v13 +// see: https://nextjs.org/docs/app/building-your-application/upgrading/app-router-migration#link-component +export const NavLink = styled.span<{ $active?: boolean }>` + cursor: pointer; + color: var(--lido-color-secondary); + font-size: ${({ theme }) => theme.fontSizesMap.xxxs}px; + line-height: 1.7em; + font-weight: 800; + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + text-transform: uppercase; + text-decoration: none !important; + letter-spacing: 0.04em; + opacity: ${(props) => (props.$active ? 1 : 0.7)}; + + gap: 10px; + ${NAV_MOBILE_MEDIA} { + gap: 7px; + } + + :hover { + opacity: 1; + color: var(--lido-color-secondary); + } + + // TODO: Not actual - remove + :visited { + color: var(--lido-color-secondary); + } + + svg { + fill: ${({ $active }) => + $active ? `var(--lido-color-primary)` : `var(--lido-color-secondary)`}; + } + + ${NAV_MOBILE_MEDIA} { + flex-direction: column; + text-transform: none; + font-weight: 500; + font-size: ${({ theme }) => theme.fontSizesMap.xxxs}px; + line-height: 1.2em; + letter-spacing: 0; + } + + ${CounterStyle} { + opacity: ${(props) => (props.$active ? 1 : 0.8)}; + } +`; diff --git a/shared/layout/navigation/use-nav-items.tsx b/shared/layout/header/components/navigation/use-nav-items.tsx similarity index 100% rename from shared/layout/navigation/use-nav-items.tsx rename to shared/layout/header/components/navigation/use-nav-items.tsx diff --git a/shared/layout/header/dummy-header.tsx b/shared/layout/header/dummy-header.tsx index 3bbc109b..a5313c2f 100644 --- a/shared/layout/header/dummy-header.tsx +++ b/shared/layout/header/dummy-header.tsx @@ -1,13 +1,26 @@ +import { ReactComponent as HomeIcon } from 'assets/icons/home.svg'; +import Link from 'next/link'; import { FC } from 'react'; +import { LogoLido } from 'shared/components'; import HeaderTheme from './components/header-theme'; -import { Logos } from './components/logos'; -import { HeaderActionsStyle, HeaderStyle } from './styles'; +import { Nav, NavLink } from './components/navigation/styles'; +import { HeaderActionsStyle, HeaderContentStyle, HeaderStyle } from './styles'; export const DummyHeader: FC = () => ( - - - - - + + + + + + + + ); diff --git a/shared/layout/header/header.tsx b/shared/layout/header/header.tsx index fe119cdb..83b94b29 100644 --- a/shared/layout/header/header.tsx +++ b/shared/layout/header/header.tsx @@ -1,27 +1,31 @@ import { FC } from 'react'; +import { LogoLido } from 'shared/components'; import { config } from 'config'; +import { AlertContainer } from 'shared/alerts'; import HeaderChain from './components/header-chain'; import HeaderEaMember from './components/header-ea-member'; import HeaderNodeOperator from './components/header-node-operator'; import { HeaderSettingsButton } from './components/header-settings-button'; import HeaderTheme from './components/header-theme'; import HeaderWallet from './components/header-wallet'; -import { Logos } from './components/logos'; -import { HeaderActionsStyle, HeaderStyle } from './styles'; -import { HeaderBurger } from './components/header-burger'; +import { Navigation } from './components/navigation/navigation'; +import { HeaderActionsStyle, HeaderContentStyle, HeaderStyle } from './styles'; export const Header: FC = () => ( - - - - - - - - {config.ipfsMode && } - - - + + + + + + + + + + {config.ipfsMode && } + + + + ); diff --git a/shared/layout/header/styles.tsx b/shared/layout/header/styles.tsx index 011822aa..466b591e 100644 --- a/shared/layout/header/styles.tsx +++ b/shared/layout/header/styles.tsx @@ -1,45 +1,33 @@ -import { Divider, ThemeToggler } from '@lidofinance/lido-ui'; +import { Container, ContainerProps, ThemeToggler } from '@lidofinance/lido-ui'; +import { LogoLidoStyle } from 'shared/components/logos/styles'; import styled, { keyframes } from 'styled-components'; import { NAV_MOBILE_MEDIA } from 'styles/constants'; -export const HeaderStyle = styled.header` - grid-area: header; - position: relative; - align-self: center; - +export const HeaderContentStyle = styled.div` display: flex; align-items: center; - flex-wrap: nowrap; + flex-wrap: wrap; row-gap: 8px; - margin-block: 18px; - - ${NAV_MOBILE_MEDIA} { - position: fixed; - z-index: 99; - top: 0; - left: 0; - right: 0; - padding: 18px 20px; - margin: 0; - background: var(--lido-color-background); - transition: box-shadow 0.3s ease; - html:is([data-scroll]):not([data-scroll='0']) & { - box-shadow: 0px -1px 4px var(--lido-color-shadowDark); - } + @media screen and (max-width: 880px) { + flex-wrap: nowrap; + } - nav[aria-expanded='true'] + & { - background: none; - box-shadow: none; - transition: - box-shadow 0.3s ease, - background 0.3s ease-in-out; - } + ${LogoLidoStyle} { + height: 44px; } `; +export const HeaderStyle = styled((props: ContainerProps) => ( + +))` + position: relative; + padding-top: 18px; + padding-bottom: 18px; +`; + export const HeaderActionsStyle = styled.div` position: relative; margin-left: auto; @@ -48,8 +36,10 @@ export const HeaderActionsStyle = styled.div` flex-shrink: 1; gap: ${({ theme }) => theme.spaceMap.sm}px; - flex-wrap: nowrap; - justify-content: end; + ${({ theme }) => theme.mediaQueries.lg} { + flex-wrap: wrap; + justify-content: end; + } `; export const HeaderWalletChainWrapper = styled.div` @@ -91,30 +81,6 @@ export const IPFSInfoBoxOnlyDesktopWrapper = styled.div` } `; -export const ThemeTogglerStyle = styled(ThemeToggler)<{ $always?: boolean }>` +export const ThemeTogglerStyle = styled(ThemeToggler)` margin: 0; - - ${NAV_MOBILE_MEDIA} { - display: ${({ $always }) => ($always ? 'inline-grid' : 'none')}; - } -`; - -export const LogosStyle = styled.div` - display: flex; - gap: 12px; - align-items: center; - - height: 28px; - margin-block: 8px; - align-self: start; - - ${({ theme }) => theme.mediaQueries.md} { - > :not(:nth-child(1)) { - display: none; - } - } -`; - -export const LogoDivider = styled(Divider).attrs({ type: 'vertical' })` - opacity: 0.6; `; diff --git a/shared/layout/layout.tsx b/shared/layout/layout.tsx index 455b914d..d61b2d90 100644 --- a/shared/layout/layout.tsx +++ b/shared/layout/layout.tsx @@ -1,23 +1,23 @@ import { FC, PropsWithChildren, ReactNode, useEffect } from 'react'; -import { AlertContainer } from 'shared/alerts'; +import { ContainerProps } from '@lidofinance/lido-ui'; + import { trackMatomoEvent, WithMatomoEvent } from 'utils'; import { Footer } from './footer/footer'; import { DummyHeader } from './header/dummy-header'; import { Header } from './header/header'; -import { Navigation } from './navigation/navigation'; +import { Main } from './main/main'; import { Content, Heading, - LayoutStyle, LayoutSubTitleStyle, LayoutTitleStyle, - Main, } from './styles'; type Props = { title?: ReactNode; subtitle?: ReactNode; + containerSize?: ContainerProps['size']; dummy?: boolean; }; @@ -26,6 +26,7 @@ export const Layout: FC>> = ({ dummy, title, subtitle, + containerSize, matomoEvent, }) => { const titlesCount = [title, subtitle].filter(Boolean).length; @@ -35,18 +36,9 @@ export const Layout: FC>> = ({ }, [matomoEvent]); return ( - - {dummy ? ( - - ) : ( - <> - -
    - - - )} - -
    + <> + {dummy ? :
    } +
    {title && {title}} {subtitle && {subtitle}} @@ -54,6 +46,6 @@ export const Layout: FC>> = ({ {children}
  3. -

    + )} diff --git a/features/view-keys/view-keys-section/keys-table.tsx b/features/view-keys/view-keys-section/keys-table.tsx index 49b8ec78..c285114d 100644 --- a/features/view-keys/view-keys-section/keys-table.tsx +++ b/features/view-keys/view-keys-section/keys-table.tsx @@ -1,4 +1,4 @@ -import { Tbody, Td, Text, Th, Thead, Tr } from '@lidofinance/lido-ui'; +import { Tbody, Td, Th, Thead, Tr } from '@lidofinance/lido-ui'; import { FC } from 'react'; import { Address, @@ -21,7 +21,6 @@ export const KeysTable: FC = ({ keys }) => { - # Key Status Comment @@ -30,11 +29,6 @@ export const KeysTable: FC = ({ keys }) => { {sortedKeys?.map(({ key, index, statuses }) => ( - - - {index + 1} - -
    { @@ -9,10 +9,8 @@ export const ViewKeys = () => { return ( <> - - - - + + diff --git a/providers/inpage-navigation.tsx b/providers/inpage-navigation.tsx index 345111f5..5068d5b1 100644 --- a/providers/inpage-navigation.tsx +++ b/providers/inpage-navigation.tsx @@ -7,17 +7,22 @@ import { useMemo, useCallback, useEffect, + useLayoutEffect, } from 'react'; import invariant from 'tiny-invariant'; import { useRouter } from 'next/router'; import { config } from 'config'; +import { lockScroll, saveScrollPosition, unlockScroll } from 'utils'; +import { debounce } from 'lodash'; export type InpageNavigationContextValue = { hashNav: string; navigateInpageAnchor: (e: React.MouseEvent) => void; resetInpageAnchor: () => void; resetSpecificAnchor: (hash: string) => void; + expanded: boolean; + toggleExpanded: () => void; }; const InpageNavigationContext = @@ -77,14 +82,40 @@ export const InpageNavigationProvider: FC = ({ [resetInpageAnchor, hashNav], ); + const [expanded, setExpanded] = useState(false); + const toggleExpanded = useCallback(() => setExpanded((prev) => !prev), []); + + useLayoutEffect(() => { + if (!expanded) return; + lockScroll(); + return unlockScroll; + }, [expanded]); + + useEffect(() => { + const handleScroll = debounce(saveScrollPosition, 10); + window.addEventListener('scroll', handleScroll, { passive: true }); + return () => { + window.removeEventListener('scroll', handleScroll); + }; + }, []); + const value = useMemo( () => ({ hashNav, navigateInpageAnchor, resetInpageAnchor, resetSpecificAnchor, + expanded, + toggleExpanded, }), - [hashNav, navigateInpageAnchor, resetInpageAnchor, resetSpecificAnchor], + [ + expanded, + hashNav, + navigateInpageAnchor, + resetInpageAnchor, + resetSpecificAnchor, + toggleExpanded, + ], ); return ( diff --git a/shared/alerts/components/alert-container.tsx b/shared/alerts/components/alert-container.tsx index 3ac61dec..b13db2ff 100644 --- a/shared/alerts/components/alert-container.tsx +++ b/shared/alerts/components/alert-container.tsx @@ -4,26 +4,30 @@ import styled from 'styled-components'; import { useAlertActions } from '../alert-provider'; const AlertContainerStyled = styled.div` - position: absolute; - top: 100%; - right: 32px; - width: 300px; - display: flex; flex-direction: column; gap: ${({ theme }) => theme.spaceMap.md}px; - @media screen and (max-width: 1286px) { - position: static; - width: initial; - max-width: 590px; /* like Main */ - margin: 0 auto; - padding: 0 20px; - } + margin: 0 auto; :not(:empty) { margin-top: 20px; } + + grid-area: none; + position: absolute; + right: 32px; + top: 80px; + width: calc(50% - 350px); + max-width: 300px; + grid-area: none; + + ${({ theme }) => theme.mediaQueries.xl} { + grid-area: alerts; + position: static; + width: 100%; + max-width: var(--layout-main-width); + } `; export const AlertContainer: FC = () => { diff --git a/shared/components/address/address.tsx b/shared/components/address/address.tsx index 5a04b040..d7f1a064 100644 --- a/shared/components/address/address.tsx +++ b/shared/components/address/address.tsx @@ -1,38 +1,64 @@ -import { AddressProps, Identicon, Tooltip } from '@lidofinance/lido-ui'; -import { FC, ReactNode } from 'react'; +import { + AddressProps, + Address as AddressComponent, + Identicon, + Text, + Tooltip, +} from '@lidofinance/lido-ui'; +import { ComponentProps, FC, ReactNode } from 'react'; import { EtherscanAddressLink } from '../external-icon-link'; -import { AddressStyle, AddressContainerStyle } from './styles'; +import { AddressContainerStyle } from './styles'; type Props = { showIcon?: boolean; - bold?: boolean; + big?: boolean; link?: ReactNode; -} & Partial; +} & Pick, 'weight' | 'size' | 'color'> & + Partial; export const Address: FC = ({ address = '', symbols = 6, showIcon = false, - bold = false, + big = false, + weight, + size = 'xs', + color, link, -}) => ( - <> - {address && ( - - {showIcon && } - {symbols === 0 ? ( - - ) : ( - - - - )} - {link ?? } - - )} - -); +}) => { + const component = ( + + + + ); + return ( + <> + {address && ( + + {showIcon && } + {symbols === 0 ? ( + component + ) : ( + + {component} + + )} + {link ?? } + + )} + + ); +}; diff --git a/shared/components/address/styles.tsx b/shared/components/address/styles.tsx index 7f62a0c3..22e4dcce 100644 --- a/shared/components/address/styles.tsx +++ b/shared/components/address/styles.tsx @@ -1,30 +1,25 @@ -import { Address } from '@lidofinance/lido-ui'; import styled, { css } from 'styled-components'; import { LinkStyled } from '../matomo-link/styles'; import { StackStyle } from '../stack/style'; -export const AddressStyle = styled(Address)<{ $bold?: boolean }>` - ${({ $bold }) => - $bold && - css` - font-weight: bold; - `} -`; - export const AddressContainerStyle = styled(StackStyle).attrs({ $gap: 'xs', $align: 'center', -})<{ $bold?: boolean }>` +})<{ $big?: boolean }>` display: inline-flex; + > span > span { + font-weight: inherit; + } + ${LinkStyled} { svg { display: inline-flex; flex-shrink: 0; } - ${({ $bold }) => - !$bold && + ${({ $big }) => + !$big && css` &, &:visited { diff --git a/shared/components/inverse-theme-provider/inverse-theme-provider.tsx b/shared/components/inverse-theme-provider/inverse-theme-provider.tsx index b1f34cda..2825f0c4 100644 --- a/shared/components/inverse-theme-provider/inverse-theme-provider.tsx +++ b/shared/components/inverse-theme-provider/inverse-theme-provider.tsx @@ -1,6 +1,7 @@ import { themeDark, themeLight, + ThemeName, ThemeProvider, useThemeToggle, } from '@lidofinance/lido-ui'; @@ -9,7 +10,9 @@ import { FC, PropsWithChildren } from 'react'; export const InverseThemeProvider: FC = ({ children }) => { const { themeName } = useThemeToggle(); return ( - + {children} ); diff --git a/shared/components/logos/logos.tsx b/shared/components/logos/logos.tsx index 00ff2bb4..543367ed 100644 --- a/shared/components/logos/logos.tsx +++ b/shared/components/logos/logos.tsx @@ -1,5 +1,4 @@ -import { LidoLogo } from '@lidofinance/lido-ui'; -import Link from 'next/link'; +import { LidoLogo, Link } from '@lidofinance/lido-ui'; import { FC } from 'react'; import { LogoLidoStyle } from './styles'; @@ -7,7 +6,7 @@ import { LogoLidoStyle } from './styles'; export const LogoLido: FC = () => ( - + ); diff --git a/shared/components/logos/styles.tsx b/shared/components/logos/styles.tsx index dbbd3672..4092930b 100644 --- a/shared/components/logos/styles.tsx +++ b/shared/components/logos/styles.tsx @@ -8,12 +8,12 @@ export const LogoLidoStyle = styled.div` flex-shrink: 0; cursor: pointer; - ${({ theme }) => theme.mediaQueries.md} { + ${({ theme }) => theme.mediaQueries.lg} { width: 14px; justify-content: flex-start; } - ${({ theme }) => theme.mediaQueries.lg} { - align-self: start; + span { + display: block; } `; diff --git a/shared/components/status-chip/style.ts b/shared/components/status-chip/style.ts index b78d5dee..d36c0786 100644 --- a/shared/components/status-chip/style.ts +++ b/shared/components/status-chip/style.ts @@ -24,6 +24,7 @@ export const StatusStyle = styled.div<{ $variant?: Variants }>` width: fit-content; padding: 4px 12px; text-align: center; + white-space: nowrap; border-radius: ${({ theme }) => theme.borderRadiusesMap.xl}px; background: color-mix(in srgb, currentColor 15%, transparent); diff --git a/shared/hooks/use-show-rule.ts b/shared/hooks/use-show-rule.ts index 2f259cce..b858a03a 100644 --- a/shared/hooks/use-show-rule.ts +++ b/shared/hooks/use-show-rule.ts @@ -37,9 +37,9 @@ export const useShowRule = () => { case 'IS_CONNECTED_WALLET': return isConnectedWallet; case 'NOT_NODE_OPERATOR': - return !nodeOperator; + return isConnectedWallet && !nodeOperator; case 'IS_NODE_OPERATOR': - return !!nodeOperator; + return !!nodeOperator && isConnectedWallet; case 'CAN_CREATE': return !!canCreateNO; case 'HAS_MANAGER_ROLE': diff --git a/shared/layout/footer/footer.tsx b/shared/layout/footer/footer.tsx index 3cf3811f..f4576f85 100644 --- a/shared/layout/footer/footer.tsx +++ b/shared/layout/footer/footer.tsx @@ -3,13 +3,7 @@ import { FC } from 'react'; import { getExternalLinks } from 'consts/external-links'; import { LogoLido, Stack } from 'shared/components'; -import { - FooterDivider, - FooterLink, - FooterStyle, - LinkDivider, - Version, -} from './styles'; +import { FooterLink, FooterStyle, LinkDivider, Version } from './styles'; const getVersionInfo = () => { const { version, branch } = buildInfo; @@ -44,7 +38,7 @@ const { feedbackForm } = getExternalLinks(); // TODO: matomo events export const Footer: FC = () => { return ( - + @@ -58,7 +52,6 @@ export const Footer: FC = () => { Feedback form {label} - ); }; diff --git a/shared/layout/footer/styles.tsx b/shared/layout/footer/styles.tsx index 1fa8b9c3..00d7c000 100644 --- a/shared/layout/footer/styles.tsx +++ b/shared/layout/footer/styles.tsx @@ -1,9 +1,10 @@ -import { Container, Link } from '@lidofinance/lido-ui'; +import { Link } from '@lidofinance/lido-ui'; import styled from 'styled-components'; import { NAV_MOBILE_MEDIA } from 'styles/constants'; -export const FooterStyle = styled(Container)` +export const FooterStyle = styled.footer` + grid-area: footer; position: relative; box-sizing: border-box; color: var(--lido-color-text); @@ -12,14 +13,23 @@ export const FooterStyle = styled(Container)` flex-wrap: wrap; column-gap: 32px; - width: 100%; - max-width: 1424px; - padding: 24px 32px; + justify-content: center; + align-self: center; + margin-block: 24px; - ${NAV_MOBILE_MEDIA} { - margin-bottom: 60px; - padding: 20px 18px; - justify-content: center; + &::before { + content: ''; + position: absolute; + top: -23px; + left: 0; + width: 100%; + height: 1px; + background: var(--lido-color-popupMenuItemBgActiveHover); + opacity: 0.12; + + ${NAV_MOBILE_MEDIA} { + display: none; + } } `; @@ -60,20 +70,6 @@ export const LinkDivider = styled.div` margin: 2px 16px; `; -export const FooterDivider = styled.div` - position: absolute; - top: 0; - left: 32px; - width: calc(100% - 64px); - height: 1px; - background: var(--lido-color-popupMenuItemBgActiveHover); - opacity: 0.12; - - ${NAV_MOBILE_MEDIA} { - display: none; - } -`; - export const Version = styled(FooterLink)` margin-left: auto; padding: 2px 5px; diff --git a/shared/layout/header/components/header-burger.tsx b/shared/layout/header/components/header-burger.tsx new file mode 100644 index 00000000..cd1de1d2 --- /dev/null +++ b/shared/layout/header/components/header-burger.tsx @@ -0,0 +1,64 @@ +import { Button } from '@lidofinance/lido-ui'; +import { useInpageNavigation } from 'providers/inpage-navigation'; +import { FC } from 'react'; +import styled from 'styled-components'; +import { NAV_MOBILE_MEDIA } from 'styles/constants'; + +export const HeaderBurger: FC = () => { + const { expanded, toggleExpanded } = useInpageNavigation(); + return ( + + + + + + ); +}; + +export const BurgerLine = styled.div` + width: 18px; + height: 2px; + border-radius: 2px; + margin-block: 4px; + + background-color: var(--lido-color-text); + transition: + transform ease ${({ theme }) => theme.duration.norm}, + opacity ease ${({ theme }) => theme.duration.norm}; +`; + +export const BurgerButtonStyle = styled(Button).attrs({ + size: 'xs', + variant: 'text', + color: 'secondary', +})` + display: none; + + width: 44px; + height: 44px; + min-width: unset; + align-items: center; + justify-content: center; + flex-shrink: 1; + padding: 0; + + ${NAV_MOBILE_MEDIA} { + display: flex; + } + + &[aria-expanded='true'] ${BurgerLine} { + :nth-child(1) { + transform: translateY(6px) rotate(-45deg); + } + :nth-child(2) { + opacity: 0; + } + :nth-child(3) { + transform: translateY(-6px) rotate(45deg); + } + } +`; diff --git a/shared/layout/header/components/header-theme.tsx b/shared/layout/header/components/header-theme.tsx index b4aabe4a..b90ad0d5 100644 --- a/shared/layout/header/components/header-theme.tsx +++ b/shared/layout/header/components/header-theme.tsx @@ -2,12 +2,18 @@ import { useRouter } from 'next/router'; import { FC } from 'react'; import { ThemeTogglerStyle } from '../styles'; -const HeaderTheme: FC = () => { +const HeaderTheme: FC<{ showAlways?: boolean }> = ({ showAlways }) => { const router = useRouter(); const queryTheme = router?.query?.theme; - return <>{!queryTheme && }; + return ( + <> + {!queryTheme && ( + + )} + + ); }; export default HeaderTheme; diff --git a/shared/layout/header/components/logos.tsx b/shared/layout/header/components/logos.tsx new file mode 100644 index 00000000..01ea3a01 --- /dev/null +++ b/shared/layout/header/components/logos.tsx @@ -0,0 +1,14 @@ +import { Link, Text } from '@lidofinance/lido-ui'; +import { FC } from 'react'; +import { LogoLido } from 'shared/components'; +import { LogoDivider, LogosStyle } from '../styles'; + +export const Logos: FC = () => ( + + + + + CSM + + +); diff --git a/shared/layout/header/components/navigation/navigation.tsx b/shared/layout/header/components/navigation/navigation.tsx deleted file mode 100644 index 6429deb6..00000000 --- a/shared/layout/header/components/navigation/navigation.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { FC, memo } from 'react'; -import { Stack } from 'shared/components'; -import { useRouterPath } from 'shared/hooks'; -import { LocalLink } from 'shared/navigate'; -import { getIsActivePath } from 'utils/path'; -import { Nav, NavLink } from './styles'; -import { useNavItems } from './use-nav-items'; - -export const Navigation: FC = memo(() => { - const routes = useNavItems(); - - const pathname = useRouterPath(); - - return ( - - ); -}); diff --git a/shared/layout/header/components/navigation/styles.tsx b/shared/layout/header/components/navigation/styles.tsx deleted file mode 100644 index 7adab00c..00000000 --- a/shared/layout/header/components/navigation/styles.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import { CounterStyle } from 'shared/components/counter/styles'; -import styled, { css } from 'styled-components'; - -import { NAV_MOBILE_HEIGHT, NAV_MOBILE_MEDIA } from 'styles/constants'; - -export const desktopCss = css` - margin: 0 46px; - display: flex; - gap: 32px; - - svg { - flex-shrink: 0; - } -`; - -const mobileCss = css` - margin: 0; - position: fixed; - bottom: 0; - left: 0; - right: 0; - padding: 8px; - background-color: var(--lido-color-foreground); - display: flex; - gap: 32px; - justify-content: space-around; - align-items: center; - border-top: 1px solid var(--lido-color-border); - height: ${NAV_MOBILE_HEIGHT}px; -`; - -export const Nav = styled.div` - ${desktopCss} - // mobile kicks in on a bit higher width for nav - ${NAV_MOBILE_MEDIA} { - ${mobileCss} - } - align-items: center; - z-index: 6; -`; - -// Not wrapping inside in IPFS mode -// Also avoid problems with migrate to Next v13 -// see: https://nextjs.org/docs/app/building-your-application/upgrading/app-router-migration#link-component -export const NavLink = styled.span<{ $active?: boolean }>` - cursor: pointer; - color: var(--lido-color-secondary); - font-size: ${({ theme }) => theme.fontSizesMap.xxxs}px; - line-height: 1.7em; - font-weight: 800; - display: flex; - flex-direction: row; - justify-content: center; - align-items: center; - text-transform: uppercase; - text-decoration: none !important; - letter-spacing: 0.04em; - opacity: ${(props) => (props.$active ? 1 : 0.7)}; - - gap: 10px; - ${NAV_MOBILE_MEDIA} { - gap: 7px; - } - - :hover { - opacity: 1; - color: var(--lido-color-secondary); - } - - // TODO: Not actual - remove - :visited { - color: var(--lido-color-secondary); - } - - svg { - fill: ${({ $active }) => - $active ? `var(--lido-color-primary)` : `var(--lido-color-secondary)`}; - } - - ${NAV_MOBILE_MEDIA} { - flex-direction: column; - text-transform: none; - font-weight: 500; - font-size: ${({ theme }) => theme.fontSizesMap.xxxs}px; - line-height: 1.2em; - letter-spacing: 0; - } - - ${CounterStyle} { - opacity: ${(props) => (props.$active ? 1 : 0.8)}; - } -`; diff --git a/shared/layout/header/dummy-header.tsx b/shared/layout/header/dummy-header.tsx index a5313c2f..3bbc109b 100644 --- a/shared/layout/header/dummy-header.tsx +++ b/shared/layout/header/dummy-header.tsx @@ -1,26 +1,13 @@ -import { ReactComponent as HomeIcon } from 'assets/icons/home.svg'; -import Link from 'next/link'; import { FC } from 'react'; -import { LogoLido } from 'shared/components'; import HeaderTheme from './components/header-theme'; -import { Nav, NavLink } from './components/navigation/styles'; -import { HeaderActionsStyle, HeaderContentStyle, HeaderStyle } from './styles'; +import { Logos } from './components/logos'; +import { HeaderActionsStyle, HeaderStyle } from './styles'; export const DummyHeader: FC = () => ( - - - - - - - - + + + + + ); diff --git a/shared/layout/header/header.tsx b/shared/layout/header/header.tsx index 83b94b29..fe119cdb 100644 --- a/shared/layout/header/header.tsx +++ b/shared/layout/header/header.tsx @@ -1,31 +1,27 @@ import { FC } from 'react'; -import { LogoLido } from 'shared/components'; import { config } from 'config'; -import { AlertContainer } from 'shared/alerts'; import HeaderChain from './components/header-chain'; import HeaderEaMember from './components/header-ea-member'; import HeaderNodeOperator from './components/header-node-operator'; import { HeaderSettingsButton } from './components/header-settings-button'; import HeaderTheme from './components/header-theme'; import HeaderWallet from './components/header-wallet'; -import { Navigation } from './components/navigation/navigation'; -import { HeaderActionsStyle, HeaderContentStyle, HeaderStyle } from './styles'; +import { Logos } from './components/logos'; +import { HeaderActionsStyle, HeaderStyle } from './styles'; +import { HeaderBurger } from './components/header-burger'; export const Header: FC = () => ( - - - - - - - - - - {config.ipfsMode && } - - - - + + + + + + + + {config.ipfsMode && } + + + ); diff --git a/shared/layout/header/styles.tsx b/shared/layout/header/styles.tsx index 466b591e..011822aa 100644 --- a/shared/layout/header/styles.tsx +++ b/shared/layout/header/styles.tsx @@ -1,31 +1,43 @@ -import { Container, ContainerProps, ThemeToggler } from '@lidofinance/lido-ui'; -import { LogoLidoStyle } from 'shared/components/logos/styles'; +import { Divider, ThemeToggler } from '@lidofinance/lido-ui'; import styled, { keyframes } from 'styled-components'; import { NAV_MOBILE_MEDIA } from 'styles/constants'; -export const HeaderContentStyle = styled.div` +export const HeaderStyle = styled.header` + grid-area: header; + position: relative; + align-self: center; + display: flex; align-items: center; + flex-wrap: nowrap; - flex-wrap: wrap; row-gap: 8px; + margin-block: 18px; - @media screen and (max-width: 880px) { - flex-wrap: nowrap; - } + ${NAV_MOBILE_MEDIA} { + position: fixed; + z-index: 99; + top: 0; + left: 0; + right: 0; + padding: 18px 20px; + margin: 0; + background: var(--lido-color-background); + transition: box-shadow 0.3s ease; - ${LogoLidoStyle} { - height: 44px; - } -`; + html:is([data-scroll]):not([data-scroll='0']) & { + box-shadow: 0px -1px 4px var(--lido-color-shadowDark); + } -export const HeaderStyle = styled((props: ContainerProps) => ( - -))` - position: relative; - padding-top: 18px; - padding-bottom: 18px; + nav[aria-expanded='true'] + & { + background: none; + box-shadow: none; + transition: + box-shadow 0.3s ease, + background 0.3s ease-in-out; + } + } `; export const HeaderActionsStyle = styled.div` @@ -36,10 +48,8 @@ export const HeaderActionsStyle = styled.div` flex-shrink: 1; gap: ${({ theme }) => theme.spaceMap.sm}px; - ${({ theme }) => theme.mediaQueries.lg} { - flex-wrap: wrap; - justify-content: end; - } + flex-wrap: nowrap; + justify-content: end; `; export const HeaderWalletChainWrapper = styled.div` @@ -81,6 +91,30 @@ export const IPFSInfoBoxOnlyDesktopWrapper = styled.div` } `; -export const ThemeTogglerStyle = styled(ThemeToggler)` +export const ThemeTogglerStyle = styled(ThemeToggler)<{ $always?: boolean }>` margin: 0; + + ${NAV_MOBILE_MEDIA} { + display: ${({ $always }) => ($always ? 'inline-grid' : 'none')}; + } +`; + +export const LogosStyle = styled.div` + display: flex; + gap: 12px; + align-items: center; + + height: 28px; + margin-block: 8px; + align-self: start; + + ${({ theme }) => theme.mediaQueries.md} { + > :not(:nth-child(1)) { + display: none; + } + } +`; + +export const LogoDivider = styled(Divider).attrs({ type: 'vertical' })` + opacity: 0.6; `; diff --git a/shared/layout/layout.tsx b/shared/layout/layout.tsx index d61b2d90..455b914d 100644 --- a/shared/layout/layout.tsx +++ b/shared/layout/layout.tsx @@ -1,23 +1,23 @@ import { FC, PropsWithChildren, ReactNode, useEffect } from 'react'; -import { ContainerProps } from '@lidofinance/lido-ui'; - +import { AlertContainer } from 'shared/alerts'; import { trackMatomoEvent, WithMatomoEvent } from 'utils'; import { Footer } from './footer/footer'; import { DummyHeader } from './header/dummy-header'; import { Header } from './header/header'; -import { Main } from './main/main'; +import { Navigation } from './navigation/navigation'; import { Content, Heading, + LayoutStyle, LayoutSubTitleStyle, LayoutTitleStyle, + Main, } from './styles'; type Props = { title?: ReactNode; subtitle?: ReactNode; - containerSize?: ContainerProps['size']; dummy?: boolean; }; @@ -26,7 +26,6 @@ export const Layout: FC>> = ({ dummy, title, subtitle, - containerSize, matomoEvent, }) => { const titlesCount = [title, subtitle].filter(Boolean).length; @@ -36,9 +35,18 @@ export const Layout: FC>> = ({ }, [matomoEvent]); return ( - <> - {dummy ? :
    } -
    + + {dummy ? ( + + ) : ( + <> + +
    + + + )} + +
    {title && {title}} {subtitle && {subtitle}} @@ -46,6 +54,6 @@ export const Layout: FC>> = ({ {children}