diff --git a/.env.example b/.env.example index 7377b198e..48520aad5 100644 --- a/.env.example +++ b/.env.example @@ -21,32 +21,18 @@ SUPPORTED_CHAINS=1,17000,11155111 # this chain uses when a wallet is not connected DEFAULT_CHAIN=1 -# api key for ethplorer for token data -ETHPLORER_API_KEY=freekey # comma-separated trusted hosts for Content Security Policy # e.g. http://localhost:PORT for local development CSP_TRUSTED_HOSTS=https://*.lido.fi - # put "true" enable report only mode for CSP CSP_REPORT_ONLY=true - # api endpoint for reporting csp violations CSP_REPORT_URI=https://stake.lido.fi/api/csp-report -# Subgraph endpoint -SUBGRAPH_MAINNET=https://gateway-arbitrum.network.thegraph.com/api/[api-key]/subgraphs/id/Sxx812XgeKyzQPaBpR5YZWmGV5fZuBaPdh7DFhzSwiQ -SUBGRAPH_HOLESKY= -SUBGRAPH_SEPOLIA= - -SUBGRAPH_REQUEST_TIMEOUT=5000 - # allow some state overrides from browser console for QA ENABLE_QA_HELPERS=false -# 1inch API token to power /api/oneinch-rate -ONE_INCH_API_KEY= - REWARDS_BACKEND=http://127.0.0.1:4000 # rate limit diff --git a/assets/dvv-banner/dvv-banner-lido-logo-dark.svg b/assets/dvv-banner/dvv-banner-lido-logo-dark.svg new file mode 100644 index 000000000..213010aec --- /dev/null +++ b/assets/dvv-banner/dvv-banner-lido-logo-dark.svg @@ -0,0 +1 @@ + diff --git a/assets/dvv-banner/dvv-banner-lido-logo-light.svg b/assets/dvv-banner/dvv-banner-lido-logo-light.svg new file mode 100644 index 000000000..7b8a0392a --- /dev/null +++ b/assets/dvv-banner/dvv-banner-lido-logo-light.svg @@ -0,0 +1 @@ + diff --git a/assets/dvv-banner/dvv-banner-partners-logo-dark.svg b/assets/dvv-banner/dvv-banner-partners-logo-dark.svg new file mode 100644 index 000000000..e4c997e01 --- /dev/null +++ b/assets/dvv-banner/dvv-banner-partners-logo-dark.svg @@ -0,0 +1 @@ + diff --git a/assets/dvv-banner/dvv-banner-partners-logo-light.svg b/assets/dvv-banner/dvv-banner-partners-logo-light.svg new file mode 100644 index 000000000..653fcb552 --- /dev/null +++ b/assets/dvv-banner/dvv-banner-partners-logo-light.svg @@ -0,0 +1 @@ + diff --git a/assets/icons/error-triangle.svg b/assets/icons/error-triangle.svg new file mode 100644 index 000000000..3ba424fc2 --- /dev/null +++ b/assets/icons/error-triangle.svg @@ -0,0 +1,3 @@ + + + diff --git a/config/groups/stake.ts b/config/groups/stake.ts index f687764cb..ebebb908a 100644 --- a/config/groups/stake.ts +++ b/config/groups/stake.ts @@ -1,6 +1,5 @@ import { BigNumber } from 'ethers'; import { parseEther } from '@ethersproject/units'; -import { AddressZero } from '@ethersproject/constants'; import { StakeSwapDiscountIntegrationKey } from 'features/stake/swap-discount-banner'; @@ -26,11 +25,11 @@ export const STAKE_GASLIMIT_FALLBACK = BigNumber.from( ), ); -export const STAKE_WIDGET_METRIC_SUFFIX = '01'; +export const LIDO_ADDRESS = '0x11D00000000000000000000000000000000011D0'; export const STAKE_FALLBACK_REFERRAL_ADDRESS = preConfig.ipfsMode ? IPFS_REFERRAL_ADDRESS - : AddressZero; + : LIDO_ADDRESS; export const STAKE_SWAP_INTEGRATION: StakeSwapDiscountIntegrationKey = 'one-inch'; diff --git a/consts/matomo-click-events.ts b/consts/matomo-click-events.ts index 2183b84ab..b0e1f0b7c 100644 --- a/consts/matomo-click-events.ts +++ b/consts/matomo-click-events.ts @@ -15,6 +15,10 @@ export const enum MATOMO_CLICK_EVENTS_TYPES { lidoOnLidoMultichainOpportunities = 'lidoOnLidoMultichainOpportunities', vaultsBannerLearnMore = 'vaultsBannerLearnMore', vaultsBannerExploreAll = 'vaultsBannerExploreAll', + obolBannerProceed = 'obolBannerProceed', + obolBannerDVVLink = 'obolBannerDVVLink', + exploreAllStrategiesAfterStake = 'exploreAllStrategiesAfterStake', + exploreAllStrategiesAfterWrap = 'exploreAllStrategiesAfterWrap', // FAQ faqSafeWorkWithLidoAudits = 'faqSafeWorkWithLidoAudits', faqLidoEthAprEthLandingPage = 'faqLidoEthAprEthLandingPage', @@ -129,6 +133,26 @@ export const MATOMO_CLICK_EVENTS: Record< 'Push "Explore all strategies"', 'eth_widget_explore_all_strategies', ], + [MATOMO_CLICK_EVENTS_TYPES.obolBannerProceed]: [ + 'Ethereum_Staking_Widget', + 'Push "Proceed" Obol SSV', + 'eth_widget_proceed_obol_ssv', + ], + [MATOMO_CLICK_EVENTS_TYPES.obolBannerDVVLink]: [ + 'Ethereum_Staking_Widget', + 'Push "the DVV Vault" link on Obol SSV banner', + 'eth_widget_the_dvv_vault_link_obol_ssv', + ], + [MATOMO_CLICK_EVENTS_TYPES.exploreAllStrategiesAfterStake]: [ + 'Ethereum_Staking_Widget', + 'Push "Explore all strategies" after staking', + 'eth_widget_explore_all_strategies_after_staking', + ], + [MATOMO_CLICK_EVENTS_TYPES.exploreAllStrategiesAfterWrap]: [ + 'Ethereum_Staking_Widget', + 'Push "Explore all strategies" after wrap', + 'eth_widget_explore_all_strategies_after_wraping', + ], // FAQ [MATOMO_CLICK_EVENTS_TYPES.faqSafeWorkWithLidoAudits]: [ 'Ethereum_Staking_Widget', diff --git a/consts/metrics.ts b/consts/metrics.ts index 5e1ac3074..574e821f1 100644 --- a/consts/metrics.ts +++ b/consts/metrics.ts @@ -3,6 +3,5 @@ export const METRICS_PREFIX = 'eth_stake_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/features/rewards/components/address-input/address-input.tsx b/features/rewards/components/address-input/address-input.tsx new file mode 100644 index 000000000..a50daf068 --- /dev/null +++ b/features/rewards/components/address-input/address-input.tsx @@ -0,0 +1,50 @@ +import { FC, useMemo } from 'react'; +import { Input, Loader, Identicon } from '@lidofinance/lido-ui'; +import CopyAddressUrl from 'features/rewards/components/CopyAddressUrl'; +import { isValidAnyAddress } from 'features/rewards/utils'; +import { ReactComponent as ErrorTriangle } from 'assets/icons/error-triangle.svg'; + +import { AddressInputProps } from './types'; + +export const AddressInput: FC = (props) => { + const { + inputValue, + isAddressResolving, + handleInputChange, + address, + addressError: invalidENSAddress, + loading, + } = props; + + const invalidEthereumAddress = useMemo( + () => inputValue.length > 0 && !isValidAnyAddress(inputValue), + [inputValue], + ); + + return ( + handleInputChange(e.target.value)} + placeholder="Ethereum address" + leftDecorator={ + loading || isAddressResolving ? ( + + ) : invalidEthereumAddress || invalidENSAddress ? ( + + ) : address ? ( + + ) : null + } + rightDecorator={address ? : null} + spellCheck="false" + error={ + invalidEthereumAddress + ? 'Invalid ethereum address' + : invalidENSAddress + ? invalidENSAddress + : null + } + /> + ); +}; diff --git a/features/rewards/components/address-input/index.ts b/features/rewards/components/address-input/index.ts new file mode 100644 index 000000000..bca21a4fc --- /dev/null +++ b/features/rewards/components/address-input/index.ts @@ -0,0 +1 @@ +export * from './address-input'; diff --git a/features/rewards/components/address-input/types.ts b/features/rewards/components/address-input/types.ts new file mode 100644 index 000000000..2aa228f99 --- /dev/null +++ b/features/rewards/components/address-input/types.ts @@ -0,0 +1,8 @@ +export type AddressInputProps = { + inputValue: string; + isAddressResolving: boolean; + handleInputChange: (value: string) => void; + address: string; + addressError: string; + loading: boolean; +}; diff --git a/features/rewards/components/errorBlocks/ErrorBlockServer.tsx b/features/rewards/components/errorBlocks/ErrorBlockServer.tsx index 4fdcba322..633267a66 100644 --- a/features/rewards/components/errorBlocks/ErrorBlockServer.tsx +++ b/features/rewards/components/errorBlocks/ErrorBlockServer.tsx @@ -3,6 +3,6 @@ import { ErrorBlockBase } from './ErrorBlockBase'; export const ErrorBlockServer = () => ( ); diff --git a/features/rewards/components/rewardsListContent/RewardsListContent.tsx b/features/rewards/components/rewardsListContent/RewardsListContent.tsx index ff5d9de6e..a47f1a6a7 100644 --- a/features/rewards/components/rewardsListContent/RewardsListContent.tsx +++ b/features/rewards/components/rewardsListContent/RewardsListContent.tsx @@ -7,7 +7,6 @@ import { STRATEGY_LAZY } from 'consts/swr-strategies'; import { useRewardsHistory } from 'features/rewards/hooks'; import { ErrorBlockNoSteth } from 'features/rewards/components/errorBlocks/ErrorBlockNoSteth'; import { RewardsTable } from 'features/rewards/components/rewardsTable'; -import { useDappStatus } from 'shared/hooks/use-dapp-status'; import { RewardsListsEmpty } from './RewardsListsEmpty'; import { RewardsListErrorMessage } from './RewardsListErrorMessage'; @@ -18,7 +17,6 @@ import { } from './RewardsListContentStyles'; export const RewardsListContent: FC = () => { - const { isDappActive } = useDappStatus(); const { error, initialLoading, @@ -32,8 +30,7 @@ export const RewardsListContent: FC = () => { useSTETHBalance(STRATEGY_LAZY); const hasSteth = stethBalance?.gt(Zero); - if (!isDappActive || (!data && !initialLoading && !error)) - return ; + if (!data && !initialLoading && !error) return ; // showing loading when canceling requests and empty response if ( (!data && !error) || diff --git a/features/rewards/components/rewardsListContent/RewardsListErrorMessage.tsx b/features/rewards/components/rewardsListContent/RewardsListErrorMessage.tsx index d03076d56..4c8a17441 100644 --- a/features/rewards/components/rewardsListContent/RewardsListErrorMessage.tsx +++ b/features/rewards/components/rewardsListContent/RewardsListErrorMessage.tsx @@ -25,5 +25,5 @@ export const RewardsListErrorMessage: React.FC = ({ error }) => { return ; } - return ; + return ; }; diff --git a/features/rewards/components/rewardsListContent/RewardsListsEmpty.tsx b/features/rewards/components/rewardsListContent/RewardsListsEmpty.tsx index 1e8d5b9e7..78baeb4ce 100644 --- a/features/rewards/components/rewardsListContent/RewardsListsEmpty.tsx +++ b/features/rewards/components/rewardsListContent/RewardsListsEmpty.tsx @@ -29,7 +29,9 @@ export const RewardsListsEmpty: FC = () => { <> -

Connect your wallet to view your staking rewards

+

+ Connect your wallet or enter your Ethereum address to see the stats. +

{isWalletConnected ? null : ( + + + } />, { isClosableOnLedger: true, diff --git a/features/stake/stake-form/stake-form.tsx b/features/stake/stake-form/stake-form.tsx index 18330f4e8..c83eecaff 100644 --- a/features/stake/stake-form/stake-form.tsx +++ b/features/stake/stake-form/stake-form.tsx @@ -8,7 +8,7 @@ import { StakeSubmitButton } from './controls/stake-submit-button'; import { StakeFormInfo } from './stake-form-info'; import { SwapDiscountBanner } from '../swap-discount-banner'; import { StakeBlock, FormControllerStyled } from './styles'; -import { VaultsBannerInfo } from 'shared/banners/vaults-banner-info'; +import { DVVBanner } from 'shared/banners/dvv-banner'; export const StakeForm: FC = memo(() => { return ( @@ -19,7 +19,7 @@ export const StakeForm: FC = memo(() => { - + diff --git a/features/stake/stake-form/use-stake.ts b/features/stake/stake-form/use-stake.ts index 092272289..a772a63f5 100644 --- a/features/stake/stake-form/use-stake.ts +++ b/features/stake/stake-form/use-stake.ts @@ -14,11 +14,7 @@ import { useCurrentStaticRpcProvider } from 'shared/hooks/use-current-static-rpc import { isContract } from 'utils/isContract'; import { runWithTransactionLogger } from 'utils'; -import { - MockLimitReachedError, - getAddress, - applyCalldataSuffix, -} from './utils'; +import { MockLimitReachedError, getAddress } from './utils'; import { useTxModalStagesStake } from './hooks/use-tx-modal-stages-stake'; import { sendTx } from 'utils/send-tx'; @@ -41,9 +37,6 @@ export const useStake = ({ onConfirm, onRetry }: StakeOptions) => { const { providerWeb3 } = useSDK(); const { txModalStages } = useTxModalStagesStake(); - // temporary disable until Ledger is fixed - const shouldApplyCalldataSuffix = false; - return useCallback( async ({ amount, referral }: StakeArguments): Promise => { try { @@ -77,14 +70,13 @@ export const useStake = ({ onConfirm, onRetry }: StakeOptions) => { }, ); - if (shouldApplyCalldataSuffix) applyCalldataSuffix(tx); - return sendTx({ tx, isMultisig, staticProvider: staticRpcProvider, walletProvider: providerWeb3, shouldApplyGasLimitRatio: true, + shouldRoundUpGasLimit: true, }); }; @@ -128,7 +120,6 @@ export const useStake = ({ onConfirm, onRetry }: StakeOptions) => { staticRpcProvider, stethContract, onConfirm, - shouldApplyCalldataSuffix, onRetry, ], ); diff --git a/features/stake/stake-form/utils.ts b/features/stake/stake-form/utils.ts index 7ebb9daf8..5f256852a 100644 --- a/features/stake/stake-form/utils.ts +++ b/features/stake/stake-form/utils.ts @@ -1,10 +1,6 @@ -import type { PopulatedTransaction } from 'ethers'; import { isAddress } from 'ethers/lib/utils'; import type { BaseProvider } from '@ethersproject/providers'; -import { config } from 'config'; -import invariant from 'tiny-invariant'; - export const getAddress = async ( input: string, providerRpc: BaseProvider, @@ -20,15 +16,6 @@ export const getAddress = async ( throw new ReferralAddressError(); }; -// adds metrics indicator for widget tx -export const applyCalldataSuffix = (tx: PopulatedTransaction) => { - if (!config.ipfsMode) { - invariant(tx.data, 'transaction must have calldata'); - tx.data = tx.data + config.STAKE_WIDGET_METRIC_SUFFIX; - } - return tx; -}; - export class ReferralAddressError extends Error { reason: string; constructor() { diff --git a/features/wsteth/unwrap/hooks/use-tx-modal-stages-unwrap.tsx b/features/wsteth/unwrap/hooks/use-tx-modal-stages-unwrap.tsx index 3d56a8dc4..570585e29 100644 --- a/features/wsteth/unwrap/hooks/use-tx-modal-stages-unwrap.tsx +++ b/features/wsteth/unwrap/hooks/use-tx-modal-stages-unwrap.tsx @@ -4,6 +4,7 @@ import { } from 'shared/transaction-modal/hooks/use-transaction-modal-stage'; import { getGeneralTransactionModalStages } from 'shared/transaction-modal/hooks/get-general-transaction-modal-stages'; +import { VaultsBannerStrategies } from 'shared/banners/vaults-banner-strategies'; import { TxStageSignOperationAmount } from 'shared/transaction-modal/tx-stages-composed/tx-stage-amount-operation'; import { TxStageOperationSucceedBalanceShown } from 'shared/transaction-modal/tx-stages-composed/tx-stage-operation-succeed-balance-shown'; @@ -47,6 +48,7 @@ const getTxModalStagesUnwrap = ( balance={balance} balanceToken={STAGE_OPERATION_ARGS.willReceiveToken} operationText={'Unwrapping'} + footer={} />, { isClosableOnLedger: true, diff --git a/features/wsteth/wrap/hooks/use-tx-modal-stages-wrap.tsx b/features/wsteth/wrap/hooks/use-tx-modal-stages-wrap.tsx index b01d7698b..94517dfd0 100644 --- a/features/wsteth/wrap/hooks/use-tx-modal-stages-wrap.tsx +++ b/features/wsteth/wrap/hooks/use-tx-modal-stages-wrap.tsx @@ -1,3 +1,6 @@ +import { Button, Link } from '@lidofinance/lido-ui'; +import { trackEvent } from '@lidofinance/analytics-matomo'; + import { TransactionModalTransitStage, useTransactionModalStage, @@ -6,10 +9,14 @@ import { getGeneralTransactionModalStages } from 'shared/transaction-modal/hooks import { TxStageSignOperationAmount } from 'shared/transaction-modal/tx-stages-composed/tx-stage-amount-operation'; import { TxStageOperationSucceedBalanceShown } from 'shared/transaction-modal/tx-stages-composed/tx-stage-operation-succeed-balance-shown'; +import { LINK_EXPLORE_STRATEGIES } from 'shared/banners/vaults-banner-info/const'; import { getTokenDisplayName } from 'utils/getTokenDisplayName'; import type { BigNumber } from 'ethers'; import type { TokensWrappable } from 'features/wsteth/shared/types'; +import { VaultsBannerInfo } from 'shared/banners/vaults-banner-info'; + +import { MATOMO_CLICK_EVENTS } from 'consts/matomo-click-events'; const STAGE_APPROVE_ARGS = { willReceiveToken: 'wstETH', @@ -82,6 +89,22 @@ const getTxModalStagesWrap = (transitStage: TransactionModalTransitStage) => ({ balance={balance} balanceToken={'wstETH'} operationText={'Wrapping'} + footer={ + <> + +
+ + trackEvent(...MATOMO_CLICK_EVENTS.exploreAllStrategiesAfterWrap) + } + > + + + + } />, { isClosableOnLedger: true, diff --git a/global.d.ts b/global.d.ts index efc35a096..6210ca609 100644 --- a/global.d.ts +++ b/global.d.ts @@ -28,7 +28,6 @@ declare module 'next/config' { rpcUrls_1: string | undefined; rpcUrls_17000: string | undefined; rpcUrls_11155111: string | undefined; - ethplorerApiKey: string | undefined; cspTrustedHosts: string | undefined; cspReportUri: string | undefined; diff --git a/next.config.mjs b/next.config.mjs index b2ef406ef..9cd936eb3 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -146,7 +146,6 @@ export default withBundleAnalyzer({ rpcUrls_1: process.env.EL_RPC_URLS_1, rpcUrls_17000: process.env.EL_RPC_URLS_17000, rpcUrls_11155111: process.env.EL_RPC_URLS_11155111, - ethplorerApiKey: process.env.ETHPLORER_API_KEY, cspTrustedHosts: process.env.CSP_TRUSTED_HOSTS, cspReportUri: process.env.CSP_REPORT_URI, diff --git a/pages/api/ldo-stats.ts b/pages/api/ldo-stats.ts index 42555b972..b214aa172 100644 --- a/pages/api/ldo-stats.ts +++ b/pages/api/ldo-stats.ts @@ -1,52 +1,8 @@ -import { Cache } from 'memory-cache'; import { wrapRequest as wrapNextRequest } from '@lidofinance/next-api-wrapper'; +import { httpMethodGuard, HttpMethod, cors, gone } from 'utilsApi'; -import { config } from 'config'; - -import { API_DEFAULT_SUNSET_TIMESTAMP, API_ROUTES } from 'consts/api'; -import { - getLdoStats, - errorAndCacheDefaultWrappers, - responseTimeMetric, - rateLimit, - sunsetBy, - httpMethodGuard, - HttpMethod, - cors, -} from 'utilsApi'; -import Metrics from 'utilsApi/metrics'; -import { API } from 'types'; - -const cache = new Cache(); - -// Proxy for third-party API. -// Returns LDO token information -// DEPRECATED: In future will be delete!!! -const ldoStats: API = async (req, res) => { - const cachedLidoStats = cache.get(config.CACHE_LDO_STATS_KEY); - - if (cachedLidoStats) { - res.json(cachedLidoStats); - } else { - const ldoStats = await getLdoStats(); - - cache.put( - config.CACHE_LDO_STATS_KEY, - { data: ldoStats }, - config.CACHE_LDO_STATS_TTL, - ); - - res.json({ data: ldoStats }); - } -}; - +// TODO: delete after all other endpoints are deprecated on 9th september 2024 export default wrapNextRequest([ httpMethodGuard([HttpMethod.GET]), cors({ origin: ['*'], methods: [HttpMethod.GET] }), - rateLimit, - responseTimeMetric(Metrics.request.apiTimings, API_ROUTES.LDO_STATS), - sunsetBy({ - sunsetTimestamp: API_DEFAULT_SUNSET_TIMESTAMP, - }), - ...errorAndCacheDefaultWrappers, -])(ldoStats); +])(gone); diff --git a/pages/api/lido-stats.ts b/pages/api/lido-stats.ts index e1213de4c..5344b09d0 100644 --- a/pages/api/lido-stats.ts +++ b/pages/api/lido-stats.ts @@ -1,50 +1,9 @@ -import { Cache } from 'memory-cache'; import { wrapRequest as wrapNextRequest } from '@lidofinance/next-api-wrapper'; -import { config } from 'config'; -import { API_DEFAULT_SUNSET_TIMESTAMP, API_ROUTES } from 'consts/api'; -import { - getLidoStats, - errorAndCacheDefaultWrappers, - responseTimeMetric, - rateLimit, - sunsetBy, - httpMethodGuard, - HttpMethod, - cors, -} from 'utilsApi'; -import Metrics from 'utilsApi/metrics'; -import { API } from 'types'; - -const cache = new Cache(); - -// Proxy for third-party API. -// Returns steth token information -// DEPRECATED: In future will be delete!!! -export const lidoStats: API = async (req, res) => { - const cachedLidoStats = cache.get(config.CACHE_LIDO_STATS_KEY); - - if (cachedLidoStats) { - res.json(cachedLidoStats); - } else { - const lidoStats = await getLidoStats(); - cache.put( - config.CACHE_LIDO_STATS_KEY, - { data: lidoStats }, - config.CACHE_LIDO_STATS_TTL, - ); - - res.json({ data: lidoStats }); - } -}; +import { httpMethodGuard, HttpMethod, cors, gone } from 'utilsApi'; +// TODO: delete after all other endpoints are deprecated on 9th september 24 export default wrapNextRequest([ httpMethodGuard([HttpMethod.GET]), cors({ origin: ['*'], methods: [HttpMethod.GET] }), - rateLimit, - responseTimeMetric(Metrics.request.apiTimings, API_ROUTES.LIDO_STATS), - sunsetBy({ - sunsetTimestamp: API_DEFAULT_SUNSET_TIMESTAMP, - }), - ...errorAndCacheDefaultWrappers, -])(lidoStats); +])(gone); diff --git a/pages/api/lidostats.ts b/pages/api/lidostats.ts index 1d3055943..b71f23a6f 100644 --- a/pages/api/lidostats.ts +++ b/pages/api/lidostats.ts @@ -1,26 +1,9 @@ import { wrapRequest as wrapNextRequest } from '@lidofinance/next-api-wrapper'; -import { API_DEFAULT_SUNSET_TIMESTAMP, API_ROUTES } from 'consts/api'; -import { - responseTimeMetric, - errorAndCacheDefaultWrappers, - rateLimit, - sunsetBy, - httpMethodGuard, - HttpMethod, - cors, -} from 'utilsApi'; -import Metrics from 'utilsApi/metrics'; -import lidoStats from './lido-stats'; +import { httpMethodGuard, HttpMethod, cors, gone } from 'utilsApi'; -// Mirror for /lido-stats +// TODO: delete after all other endpoints are deprecated on 9th september 2024 export default wrapNextRequest([ httpMethodGuard([HttpMethod.GET]), cors({ origin: ['*'], methods: [HttpMethod.GET] }), - rateLimit, - responseTimeMetric(Metrics.request.apiTimings, API_ROUTES.LIDOSTATS), - sunsetBy({ - sunsetTimestamp: API_DEFAULT_SUNSET_TIMESTAMP, - }), - ...errorAndCacheDefaultWrappers, -])(lidoStats); +])(gone); diff --git a/shared/banners/dvv-banner/dvv-banner.tsx b/shared/banners/dvv-banner/dvv-banner.tsx new file mode 100644 index 000000000..abc5992fe --- /dev/null +++ b/shared/banners/dvv-banner/dvv-banner.tsx @@ -0,0 +1,87 @@ +import { BannerTitleText } from '../shared-banner-partials'; +import { Button, Link, useThemeToggle } from '@lidofinance/lido-ui'; +import { + Wrap, + Description, + Partners, + PartnerItem, + PartnerImage, + PartnerText, + PartnerSeparator, + Footer, + FooterText, + FooterAction, +} from './styles'; + +import { ReactComponent as IconLidoLogoLight } from 'assets/dvv-banner/dvv-banner-lido-logo-light.svg'; +import { ReactComponent as IconPartnersLogoLight } from 'assets/dvv-banner/dvv-banner-partners-logo-light.svg'; +import { ReactComponent as IconLidoLogoDark } from 'assets/dvv-banner/dvv-banner-lido-logo-dark.svg'; +import { ReactComponent as IconPartnersLogoDark } from 'assets/dvv-banner/dvv-banner-partners-logo-dark.svg'; + +import { trackEvent } from '@lidofinance/analytics-matomo'; +import { MATOMO_CLICK_EVENTS } from 'consts/matomo-click-events'; + +const LINK_DVV_VAULT = + 'https://blog.lido.fi/decentralized-validator-vault-mellow-obol-ssv/'; +const LINK_PROCEED_BUTTON = + 'https://app.mellow.finance/vaults/ethereum-dvsteth'; + +export const DVVBanner = () => { + const { themeName } = useThemeToggle(); + const isDarkTheme = themeName === 'dark'; + return ( + + New way to support decentralization + + + You can stake ETH in{' '} + trackEvent(...MATOMO_CLICK_EVENTS.obolBannerDVVLink)} + > + the DVV vault + {' '} + to get stETH rewards, gain points and help to decentralize the Lido + Protocol + + + + + + {isDarkTheme ? : } + + + stETH APR + + + + + + + {isDarkTheme ? : } + + + Obol + SSV + Mellow Points + + + + +
+ + Not financial advice. Info and APR are illustrative, actual rewards + may vary. Vaults use carries risk. By proceeding, you'll be + redirected to a third-party site. + + + trackEvent(...MATOMO_CLICK_EVENTS.obolBannerProceed)} + > + + + +
+
+ ); +}; diff --git a/shared/banners/dvv-banner/index.ts b/shared/banners/dvv-banner/index.ts new file mode 100644 index 000000000..e85526fe9 --- /dev/null +++ b/shared/banners/dvv-banner/index.ts @@ -0,0 +1 @@ +export * from './dvv-banner'; diff --git a/shared/banners/dvv-banner/styles.ts b/shared/banners/dvv-banner/styles.ts new file mode 100644 index 000000000..e44018b0a --- /dev/null +++ b/shared/banners/dvv-banner/styles.ts @@ -0,0 +1,72 @@ +import styled from 'styled-components'; +import { BannerWrap } from '../shared-banner-partials'; + +export const Wrap = styled(BannerWrap)` + background: ${({ theme }) => + theme.name === 'dark' + ? '#28282F' + : 'radial-gradient(25% 100% at 60% 10%, #ecf2ff 0%, #ebedff 100%)'}; + color: var(--lido-color-text); +`; + +export const Description = styled.div` + margin-top: 5px; + margin-bottom: 10px; + font-size: 14px; + font-weight: 400; + line-height: 24px; +`; + +export const Partners = styled.div` + margin-bottom: 10px; + display: flex; + align-items: center; + gap: 10px; +`; + +export const PartnerItem = styled.div` + display: flex; + align-items: center; + gap: 6px; + + @media (max-width: 520px) { + flex-direction: column; + justify-content: flex-start; + align-items: flex-start; + } +`; + +export const PartnerImage = styled.div` + svg { + display: block; + } +`; + +export const PartnerText = styled.div``; + +export const PartnerSeparator = styled.div` + font-weight: 700; +`; + +export const Footer = styled.div` + display: flex; + align-items: flex-end; + gap: 10px; + + ${({ theme }) => theme.mediaQueries.md} { + flex-direction: column; + } +`; + +export const FooterText = styled.div` + color: var(--lido-color-textSecondary); + font-size: 11px; + line-height: 17px; + opacity: ${({ theme }) => (theme.name === 'dark' ? '0.5' : '1')}; +`; + +export const FooterAction = styled.div` + ${({ theme }) => theme.mediaQueries.md} { + width: 100%; + } +`; diff --git a/shared/banners/shared-banner-partials/index.ts b/shared/banners/shared-banner-partials/index.ts new file mode 100644 index 000000000..0e906c3fd --- /dev/null +++ b/shared/banners/shared-banner-partials/index.ts @@ -0,0 +1 @@ +export * from './shared-banner-partials'; diff --git a/shared/banners/shared-banner-partials/shared-banner-partials.ts b/shared/banners/shared-banner-partials/shared-banner-partials.ts new file mode 100644 index 000000000..60938450d --- /dev/null +++ b/shared/banners/shared-banner-partials/shared-banner-partials.ts @@ -0,0 +1,22 @@ +import styled from 'styled-components'; + +export const BannerWrap = styled.div` + position: relative; + text-align: left; + padding: 16px; + border-radius: 16px; + background-color: ${({ theme }) => + theme.name === 'dark' ? '#28282f' : '#f2f3fc'}; +`; + +export const BannerTitleText = styled.div` + font-size: 20px; + line-height: 28px; + font-weight: 700; + color: var(--lido-color-text); + + ${({ theme }) => theme.mediaQueries.md} { + font-size: 16px; + line-height: 24px; + } +`; diff --git a/shared/banners/vaults-banner-info/const.ts b/shared/banners/vaults-banner-info/const.ts new file mode 100644 index 000000000..90e7c7247 --- /dev/null +++ b/shared/banners/vaults-banner-info/const.ts @@ -0,0 +1 @@ +export const LINK_EXPLORE_STRATEGIES = 'https://lido.fi/#defi-strategies'; diff --git a/shared/banners/vaults-banner-info/styles.ts b/shared/banners/vaults-banner-info/styles.ts index baff3cffc..0121913f9 100644 --- a/shared/banners/vaults-banner-info/styles.ts +++ b/shared/banners/vaults-banner-info/styles.ts @@ -1,17 +1,12 @@ import styled from 'styled-components'; -export const Wrap = styled.div` - position: relative; - padding: 16px; - border-radius: 16px; - background-color: ${({ theme }) => - theme.name === 'dark' ? '#28282f' : '#f2f3fc'}; -`; - +type TitleProps = { + isCompact?: boolean; +}; export const Title = styled.div` margin-bottom: 8px; - font-size: 20px; - line-height: 20px; + font-size: ${({ isCompact }: TitleProps) => (isCompact ? '16px' : '20px')}; + line-height: 1; font-weight: 700; color: var(--lido-color-text); `; diff --git a/shared/banners/vaults-banner-info/vaults-banner-info.tsx b/shared/banners/vaults-banner-info/vaults-banner-info.tsx index 52b7763d1..71a1dd554 100644 --- a/shared/banners/vaults-banner-info/vaults-banner-info.tsx +++ b/shared/banners/vaults-banner-info/vaults-banner-info.tsx @@ -15,18 +15,29 @@ import { config } from 'config'; import { MATOMO_CLICK_EVENTS } from 'consts/matomo-click-events'; import { BannerLinkButton } from '../banner-link-button'; -import { Wrap, Title, Description, Footer, Logos } from './styles'; +import { BannerWrap } from '../shared-banner-partials'; +import { Title, Description, Footer, Logos } from './styles'; const LINK_LEARN_MORE = `${config.rootOrigin}/#defi-strategies`; const linkClickHandler = () => trackEvent(...MATOMO_CLICK_EVENTS.vaultsBannerLearnMore); -export const VaultsBannerInfo = () => { +type VaultsBannerInfoProps = { + isTitleCompact?: boolean; + showLearnMoreButton?: boolean; +}; + +export const VaultsBannerInfo = ({ + isTitleCompact, + showLearnMoreButton = true, +}: VaultsBannerInfoProps) => { const { themeName } = useThemeToggle(); return ( - - Explore and participate in DeFi strategies + + + Explore and participate in DeFi strategies + Use stETH to unlock rewards through a set of carefully curated vaults @@ -41,10 +52,12 @@ export const VaultsBannerInfo = () => { {themeName === 'dark' ? : } {themeName === 'dark' ? : } - - Learn more - + {showLearnMoreButton && ( + + Learn more + + )} - + ); }; diff --git a/shared/transaction-modal/tx-stages-composed/tx-stage-operation-succeed-balance-shown.tsx b/shared/transaction-modal/tx-stages-composed/tx-stage-operation-succeed-balance-shown.tsx index 5106ccd5b..0f03692dd 100644 --- a/shared/transaction-modal/tx-stages-composed/tx-stage-operation-succeed-balance-shown.tsx +++ b/shared/transaction-modal/tx-stages-composed/tx-stage-operation-succeed-balance-shown.tsx @@ -3,7 +3,6 @@ import styled from 'styled-components'; import { useTokenAddress } from '@lido-sdk/react'; import { TOKENS } from '@lido-sdk/constants'; import { InlineLoader } from '@lidofinance/lido-ui'; -import { VaultsBannerStrategies } from 'shared/banners/vaults-banner-strategies'; import { TxAmount } from '../tx-stages-parts/tx-amount'; import { SuccessText } from '../tx-stages-parts/success-text'; import { TxStageSuccess } from '../tx-stages-basic'; @@ -28,6 +27,7 @@ type TxStageOperationSucceedBalanceShownProps = { balanceToken: string; operationText: string; txHash?: string; + footer?: React.ReactNode; }; export const TxStageOperationSucceedBalanceShown = ({ @@ -35,6 +35,7 @@ export const TxStageOperationSucceedBalanceShown = ({ balanceToken, operationText, txHash, + footer, }: TxStageOperationSucceedBalanceShownProps) => { const stethAddress = useTokenAddress(TOKENS.STETH); const wstethAddress = useTokenAddress(TOKENS.WSTETH); @@ -64,7 +65,7 @@ export const TxStageOperationSucceedBalanceShown = ({ } showEtherscan={false} - footer={} + footer={footer} /> ); }; diff --git a/tsconfig.json b/tsconfig.json index 5a7ebf58b..e27e264f0 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,7 +9,7 @@ "noEmit": true, "esModuleInterop": true, "module": "esnext", - "moduleResolution": "nodenext", + "moduleResolution": "node", "resolveJsonModule": true, "isolatedModules": true, "jsx": "preserve", diff --git a/utils/apply-round-up-gas-limit.ts b/utils/apply-round-up-gas-limit.ts new file mode 100644 index 000000000..74d05d234 --- /dev/null +++ b/utils/apply-round-up-gas-limit.ts @@ -0,0 +1,8 @@ +import { BigNumber } from 'ethers'; + +const bn1000 = BigNumber.from(1000); +const bn999 = BigNumber.from(999); + +export const applyRoundUpGasLimit = (number: BigNumber): BigNumber => + // 94567 -> 94 -> 94000 -> 94999 + number.div(bn1000).mul(bn1000).add(bn999); diff --git a/utils/send-tx.ts b/utils/send-tx.ts index a7c003a70..3b56366c6 100644 --- a/utils/send-tx.ts +++ b/utils/send-tx.ts @@ -7,6 +7,7 @@ import type { PopulatedTransaction } from 'ethers'; import { getFeeData } from './getFeeData'; import { estimateGas } from './estimate-gas'; import { applyGasLimitRatio } from 'utils/apply-gas-limit-ratio'; +import { applyRoundUpGasLimit } from 'utils/apply-round-up-gas-limit'; export type SendTxOptions = { tx: PopulatedTransaction; @@ -14,6 +15,7 @@ export type SendTxOptions = { walletProvider: Web3Provider; staticProvider: JsonRpcBatchProvider; shouldApplyGasLimitRatio?: boolean; + shouldRoundUpGasLimit?: boolean; }; export const sendTx = async ({ @@ -22,6 +24,7 @@ export const sendTx = async ({ staticProvider, walletProvider, shouldApplyGasLimitRatio = false, + shouldRoundUpGasLimit = false, }: SendTxOptions) => { if (!isMultisig) { const { maxFeePerGas, maxPriorityFeePerGas } = @@ -35,6 +38,10 @@ export const sendTx = async ({ tx.gasLimit = shouldApplyGasLimitRatio ? applyGasLimitRatio(gasLimit) : gasLimit; + + tx.gasLimit = shouldRoundUpGasLimit + ? applyRoundUpGasLimit(tx.gasLimit) + : gasLimit; } return walletProvider.getSigner().sendUncheckedTransaction(tx); }; diff --git a/utilsApi/getLdoStats.ts b/utilsApi/getLdoStats.ts deleted file mode 100644 index 577b9c15e..000000000 --- a/utilsApi/getLdoStats.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { getTokenAddress, TOKENS } from '@lido-sdk/constants'; - -import { secretConfig } from 'config'; -import { ETHPLORER_TOKEN_ENDPOINT } from 'consts/api'; -import { CHAINS } from 'consts/chains'; -import { standardFetcher } from 'utils/standardFetcher'; -import { responseTimeExternalMetricWrapper } from 'utilsApi'; - -type GetLdoStats = () => Promise; - -// DEPRECATED: In future will be delete!!! Because we don't want to use https://api.ethplorer.io/ -export const getLdoStats: GetLdoStats = async () => { - console.debug('Fetching LDO stats...'); - // IMPORTANT: ETHPLORER_TOKEN_ENDPOINT (api.ethplorer.io) works only with Mainnet chain! - const api = `${ETHPLORER_TOKEN_ENDPOINT}${getTokenAddress( - CHAINS.Mainnet as number, - TOKENS.LDO, - )}`; - const query = new URLSearchParams({ - apiKey: secretConfig.ethplorerApiKey ?? '', - }); - const url = `${api}?${query.toString()}`; - - const ldoStats = await responseTimeExternalMetricWrapper({ - payload: ETHPLORER_TOKEN_ENDPOINT, - request: () => standardFetcher(url), - }); - console.debug('LDO stats: ' + ldoStats); - - return ldoStats; -}; diff --git a/utilsApi/getLidoStats.ts b/utilsApi/getLidoStats.ts deleted file mode 100644 index d0f0598d8..000000000 --- a/utilsApi/getLidoStats.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { TOKENS, getTokenAddress, CHAINS } from '@lido-sdk/constants'; - -import { secretConfig } from 'config'; -import { ETHPLORER_TOKEN_ENDPOINT } from 'consts/api'; -import { standardFetcher } from 'utils/standardFetcher'; -import { responseTimeExternalMetricWrapper } from 'utilsApi'; - -type GetLidoStats = () => Promise; - -// DEPRECATED: In future will be delete!!! Because we don't want to use https://api.ethplorer.io/ -export const getLidoStats: GetLidoStats = async () => { - console.debug('[getLidoStats] Started fetching...'); - // IMPORTANT: ETHPLORER_TOKEN_ENDPOINT (api.ethplorer.io) works only with Mainnet chain! - const api = `${ETHPLORER_TOKEN_ENDPOINT}${getTokenAddress( - CHAINS.Mainnet, - TOKENS.STETH, - )}`; - const query = new URLSearchParams({ - apiKey: secretConfig.ethplorerApiKey ?? '', - }); - const url = `${api}?${query.toString()}`; - - const lidoStats = await responseTimeExternalMetricWrapper({ - payload: ETHPLORER_TOKEN_ENDPOINT, - request: () => standardFetcher(url), - }); - console.debug('[getLidoStats] Lido stats:', lidoStats); - return lidoStats; -}; diff --git a/utilsApi/gone.ts b/utilsApi/gone.ts new file mode 100644 index 000000000..6b872e985 --- /dev/null +++ b/utilsApi/gone.ts @@ -0,0 +1,6 @@ +import { API } from 'types/api.js'; + +export const gone: API = async (_, res) => { + res.status(410); + res.end(); +}; diff --git a/utilsApi/index.ts b/utilsApi/index.ts index de210ed39..a98b393ff 100644 --- a/utilsApi/index.ts +++ b/utilsApi/index.ts @@ -1,4 +1,4 @@ -export * from './getLdoStats'; -export * from './getLidoStats'; export * from './nextApiWrappers'; export * from './fetchApiWrapper'; +export * from './cached-proxy'; +export * from './gone'; diff --git a/utilsApi/metrics/metrics.ts b/utilsApi/metrics/metrics.ts index 18d00cb51..3c5b2337e 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 fef942f47..000000000 --- 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], - }); - } -} diff --git a/yarn.lock b/yarn.lock index 9f1bb5b2a..6bb8dd6f2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1005,6 +1005,13 @@ resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310" integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== +"@babel/runtime@^7.12.5": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.25.0.tgz#3af9a91c1b739c569d5d80cc917280919c544ecb" + integrity sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/runtime@^7.19.4", "@babel/runtime@^7.21.0": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.7.tgz#f4f0d5530e8dbdf59b3451b9b3e594b6ba082e12"