diff --git a/.github/workflows/ci-dev-goerli.yml b/.github/workflows/ci-dev-goerli.yml new file mode 100644 index 000000000..7aa7bb70c --- /dev/null +++ b/.github/workflows/ci-dev-goerli.yml @@ -0,0 +1,39 @@ +name: CI Dev Goerli + +on: + workflow_dispatch: + push: + branches: + - goerli + paths-ignore: + - '.github/**' + - 'test/**' + +permissions: + contents: read + +jobs: + # test: + # ... + + deploy: + runs-on: ubuntu-latest + # needs: test + name: Build and deploy + steps: + - name: Testnet deploy + uses: lidofinance/dispatch-workflow@v1 + env: + APP_ID: ${{ secrets.APP_ID }} + APP_PRIVATE_KEY: ${{ secrets.APP_PRIVATE_KEY }} + TARGET_REPO: 'lidofinance/infra-mainnet' + TARGET_WORKFLOW: 'deploy_testnet_ethereum_staking_widget.yaml' + TARGET: 'goerli' + + tests: + needs: deploy + if: ${{ github.event.pull_request.draft == false }} + uses: ./.github/workflows/tests.yml + secrets: inherit + with: + stand_url: https://stake.testnet.fi diff --git a/.github/workflows/ci-dev.yml b/.github/workflows/ci-dev.yml index 253181c52..e40dfcb02 100644 --- a/.github/workflows/ci-dev.yml +++ b/.github/workflows/ci-dev.yml @@ -1,4 +1,4 @@ -name: CI Dev +name: CI Dev Holesky on: workflow_dispatch: @@ -27,7 +27,7 @@ jobs: APP_ID: ${{ secrets.APP_ID }} APP_PRIVATE_KEY: ${{ secrets.APP_PRIVATE_KEY }} TARGET_REPO: 'lidofinance/infra-mainnet' - TARGET_WORKFLOW: 'deploy_testnet_ethereum_staking_widget.yaml' + TARGET_WORKFLOW: 'deploy_holesky_testnet_ethereum_staking_widget.yaml' TARGET: 'develop' tests: @@ -36,4 +36,4 @@ jobs: uses: ./.github/workflows/tests.yml secrets: inherit with: - stand_url: https://stake.testnet.fi + stand_url: https://stake-holesky.testnet.fi diff --git a/assets/icons/l2-swap.svg b/assets/icons/l2-swap.svg new file mode 100644 index 000000000..1ca848ff2 --- /dev/null +++ b/assets/icons/l2-swap.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/config/matomoClickEvents.ts b/config/matomoClickEvents.ts index 37080e726..e4e18f4ca 100644 --- a/config/matomoClickEvents.ts +++ b/config/matomoClickEvents.ts @@ -10,6 +10,9 @@ export const enum MATOMO_CLICK_EVENTS_TYPES { oneInchDiscount = 'oneInchDiscount', viewEtherscanOnStakePage = 'viewEtherscanOnStakePage', l2BannerStake = 'l2BannerStake', + l2LowFeeStake = 'l2LowFeeStake', + l2LowFeeWrap = 'l2LowFeeWrap', + l2swap = 'l2swap', // FAQ faqSafeWorkWithLidoAudits = 'faqSafeWorkWithLidoAudits', faqLidoEthAprEthLandingPage = 'faqLidoEthAprEthLandingPage', @@ -37,8 +40,6 @@ export const enum MATOMO_CLICK_EVENTS_TYPES { wrapTokenSelectETH = 'wrapTokenSelectEth', // Unwrap tab l2BannerUnwrap = 'l2BannerUnwrap', - // /rewards page - calculateRewards = 'calculateRewards', // /withdrawal page withdrawalUseLido = 'withdrawalUseLido', @@ -101,6 +102,21 @@ export const MATOMO_CLICK_EVENTS: Record< 'Push "Learn more" at the L2 banner on "Stake" tab', 'eth_widget_banner_l2_stake', ], + [MATOMO_CLICK_EVENTS_TYPES.l2LowFeeStake]: [ + 'Ethereum_Staking_Widget', + 'Push «Learn more» in Unlock Low-Fee transactions on L2 on Transaction success banner on "Stake" tab', + 'eth_widget_banner_learn_more_L2_stake', + ], + [MATOMO_CLICK_EVENTS_TYPES.l2LowFeeWrap]: [ + 'Ethereum_Staking_Widget', + 'Push «Learn more» in Unlock Low-Fee transactions on L2 on Transaction success banner on "Wrap" tab', + 'eth_widget_banner_learn_more_L2_wrap', + ], + [MATOMO_CLICK_EVENTS_TYPES.l2swap]: [ + 'Ethereum_Staking_Widget', + 'Push «Swap» in Swap ETH to wstETH on L2 banner on staking widget', + 'eth_widget_banner_swap_ETH_on_L2', + ], // FAQ [MATOMO_CLICK_EVENTS_TYPES.faqSafeWorkWithLidoAudits]: [ 'Ethereum_Staking_Widget', @@ -224,12 +240,6 @@ export const MATOMO_CLICK_EVENTS: Record< 'Push "Learn more" at the L2 banner on "Unwrap" tab', 'eth_widget_banner_l2_unwrap', ], - // /rewards page - [MATOMO_CLICK_EVENTS_TYPES.calculateRewards]: [ - 'Ethereum_Staking_Widget', - 'Push calculate reward button" ', - 'eth_widget_calculate_reward', - ], // /withdrawal page [MATOMO_CLICK_EVENTS_TYPES.withdrawalUseLido]: [ diff --git a/config/matomoWalletsEvents.ts b/config/matomoWalletsEvents.ts index 1dc53e414..f7f5d3c47 100644 --- a/config/matomoWalletsEvents.ts +++ b/config/matomoWalletsEvents.ts @@ -308,7 +308,7 @@ export const walletsMetrics: WalletsMetrics = { onClickTrust: () => { trackEvent(...MATOMO_WALLETS_EVENTS.onClickTrust); }, - onClickWC: () => { + onClickWalletconnect: () => { trackEvent(...MATOMO_WALLETS_EVENTS.onClickWC); }, onClickXdefi: () => { @@ -375,7 +375,7 @@ export const walletsMetrics: WalletsMetrics = { onConnectTrust: () => { trackEvent(...MATOMO_WALLETS_EVENTS.onConnectTrust); }, - onConnectWC: () => { + onConnectWalletconnect: () => { trackEvent(...MATOMO_WALLETS_EVENTS.onConnectWC); }, onConnectXdefi: () => { diff --git a/features/home/stake-form/stake-form.tsx b/features/home/stake-form/stake-form.tsx index 715159788..1d1585a9e 100644 --- a/features/home/stake-form/stake-form.tsx +++ b/features/home/stake-form/stake-form.tsx @@ -25,7 +25,7 @@ import { DataTableRow, Eth, } from '@lidofinance/lido-ui'; -import { OneinchInfo } from 'features/home/oneinch-info/oneinch-info'; +import { L2Swap } from 'shared/banners'; import { DATA_UNAVAILABLE } from 'config'; import { Connect } from 'shared/wallet'; import { TxStageModal, TX_OPERATION, TX_STAGE } from 'shared/components'; @@ -214,7 +214,7 @@ export const StakeForm: FC = memo(() => { ) : ( )} - + diff --git a/features/withdrawals/request/form/controls/token-amount-input-request.tsx b/features/withdrawals/request/form/controls/token-amount-input-request.tsx index ebbd7b573..ba6676cf4 100644 --- a/features/withdrawals/request/form/controls/token-amount-input-request.tsx +++ b/features/withdrawals/request/form/controls/token-amount-input-request.tsx @@ -1,12 +1,16 @@ -import { InputDecoratorTvlStake } from 'features/withdrawals/shared/input-decorator-tvl-stake'; import { useController, useWatch } from 'react-hook-form'; +import { + MATOMO_CLICK_EVENTS_TYPES, + trackMatomoEvent, +} from 'config/trackMatomoEvent'; +import { TokenAmountInputHookForm } from 'shared/hook-form/controls/token-amount-input-hook-form'; +import { InputDecoratorTvlStake } from 'features/withdrawals/shared/input-decorator-tvl-stake'; import { RequestFormInputType, useRequestFormData, } from 'features/withdrawals/request/request-form-context'; import { useTvlMessage } from 'features/withdrawals/hooks/useTvlMessage'; -import { TokenAmountInputHookForm } from 'shared/hook-form/controls/token-amount-input-hook-form'; export const TokenAmountInputRequest = () => { const token = useWatch({ name: 'token' }); @@ -26,6 +30,9 @@ export const TokenAmountInputRequest = () => { token={token} isLocked={isTokenLocked} maxValue={maxAmount} + onMaxClick={() => { + trackMatomoEvent(MATOMO_CLICK_EVENTS_TYPES.withdrawalMaxInput); + }} rightDecorator={ balanceDiff && } diff --git a/shared/banners/index.ts b/shared/banners/index.ts index db487e3ee..efbdbb5f1 100644 --- a/shared/banners/index.ts +++ b/shared/banners/index.ts @@ -1,2 +1,4 @@ export * from './curve'; export * from './modal-pool-banners'; +export * from './l2-low-fee'; +export * from './l2-swap'; diff --git a/shared/banners/l2-low-fee/index.ts b/shared/banners/l2-low-fee/index.ts new file mode 100644 index 000000000..0964a76e5 --- /dev/null +++ b/shared/banners/l2-low-fee/index.ts @@ -0,0 +1 @@ +export * from './l2-low-fee'; diff --git a/shared/banners/l2-low-fee/l2-low-fee.tsx b/shared/banners/l2-low-fee/l2-low-fee.tsx new file mode 100644 index 000000000..c4afff356 --- /dev/null +++ b/shared/banners/l2-low-fee/l2-low-fee.tsx @@ -0,0 +1,61 @@ +import { FC, useCallback } from 'react'; +import { ThemeProvider, themeDark } from '@lidofinance/lido-ui'; +import { trackEvent } from '@lidofinance/analytics-matomo'; +import { MATOMO_CLICK_EVENTS } from 'config'; + +import { + Wrapper, + L2Icons, + TextWrap, + ButtonWrap, + ButtonLinkWrap, + ButtonStyle, + ContentWrap, + TextHeader, +} from './styles'; + +const LINK = 'https://lido.fi/lido-on-l2'; + +type L2LowFeeProps = { + token: 'stETH' | 'wstETH'; +}; + +export const L2LowFee: FC = ({ token }) => { + const linkProps = { + href: LINK, + target: '_blank', + rel: 'noopener noreferrer', + }; + const isStETH = token === 'stETH'; + const linkClickHandler = useCallback(() => { + const event = isStETH + ? MATOMO_CLICK_EVENTS.l2LowFeeStake + : MATOMO_CLICK_EVENTS.l2LowFeeWrap; + trackEvent(...event); + }, [isStETH]); + + const text = isStETH + ? 'Learn about Lido on L2 opportunities on Arbitrum, Optimism, and Polygon PoS to enjoy reduced gas fees in DeFi' + : 'Bridge wstETH to Arbitrum, Optimism and Polygon PoS to enjoy low gas fees and enhanced opportunities in DeFi'; + + return ( + + + + + Unlock Low-Fee transactions on L2 + {text} + + + + + Learn more + + + + + + + + ); +}; diff --git a/shared/banners/l2-low-fee/styles.ts b/shared/banners/l2-low-fee/styles.ts new file mode 100644 index 000000000..70f3135d1 --- /dev/null +++ b/shared/banners/l2-low-fee/styles.ts @@ -0,0 +1,88 @@ +import styled from 'styled-components'; +import { Button } from '@lidofinance/lido-ui'; +import Icons from 'assets/icons/l2-swap.svg'; + +export const Wrapper = styled.div` + text-align: left; + margin-top: 16px; + position: relative; + display: flex; + align-items: center; + padding: ${({ theme }) => theme.spaceMap.lg}px; + border-radius: ${({ theme }) => theme.borderRadiusesMap.lg}px; + height: 174px; + gap: 20px; + overflow: hidden; + background-color: #07080c; + background: linear-gradient(90deg, #3487e5 0%, #006ae3 46.27%); + box-sizing: border-box; + + ${({ theme }) => theme.mediaQueries.md} { + gap: 2px; + padding: ${({ theme }) => theme.spaceMap.md}px; + } + + @media (max-width: 396px) { + cursor: pointer; + padding: ${({ theme }) => theme.spaceMap.sm}px; + } +`; + +export const L2Icons = styled.img.attrs({ + src: Icons, + alt: '', +})` + position: relative; + display: block; + width: 44px; + height: 120px; +`; + +export const ContentWrap = styled.div` + width: 100%; + height: 100%; + box-sizing: border-box; +`; + +export const TextHeader = styled.div` + font-size: 14px; + line-height: 24px; + font-weight: 700; + color: #fff; + margin-bottom: 4px; +`; + +export const TextWrap = styled.div` + flex: 1 1 auto; + color: #fff; + line-height: 20px; + font-size: 12px; + font-weight: 400; + position: relative; +`; + +export const ButtonWrap = styled.div` + display: flex; + margin-top: 12px; +`; + +export const ButtonLinkWrap = styled.a` + display: block; + + @media (max-width: 396px) { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + } +`; + +export const ButtonStyle = styled(Button)` + @media (max-width: 396px) { + display: none; + } + padding: 7px 16px; + font-size: 12px; + line-height: 20px; +`; diff --git a/shared/banners/l2-swap/index.ts b/shared/banners/l2-swap/index.ts new file mode 100644 index 000000000..b77a13524 --- /dev/null +++ b/shared/banners/l2-swap/index.ts @@ -0,0 +1 @@ +export * from './l2-swap'; diff --git a/shared/banners/l2-swap/l2-swap.tsx b/shared/banners/l2-swap/l2-swap.tsx new file mode 100644 index 000000000..071d85436 --- /dev/null +++ b/shared/banners/l2-swap/l2-swap.tsx @@ -0,0 +1,49 @@ +import { FC } from 'react'; +import { ThemeProvider, themeDark } from '@lidofinance/lido-ui'; +import { trackEvent } from '@lidofinance/analytics-matomo'; +import { MATOMO_CLICK_EVENTS } from 'config'; + +import { + Wrapper, + L2Icons, + TextWrap, + ButtonWrap, + ButtonLinkWrap, + ButtonStyle, + ContentWrap, + TextHeader, +} from './styles'; + +const CURVE_LINK = 'https://kyberswap.com/swap/ethereum/eth-to-wsteth'; + +const linkClickHandler = () => trackEvent(...MATOMO_CLICK_EVENTS.l2swap); + +export const L2Swap: FC = () => { + const linkProps = { + href: CURVE_LINK, + target: '_blank', + rel: 'noopener noreferrer', + }; + + return ( + + + + + Swap ETH to wstETH on L2 + Swap ETH to wstETH directly on L2 and use wstETH in DeFi enjoying + low gas fees opportunities + + + + + Swap + + + + + + + + ); +}; diff --git a/shared/banners/l2-swap/styles.ts b/shared/banners/l2-swap/styles.ts new file mode 100644 index 000000000..8bf7d0a4a --- /dev/null +++ b/shared/banners/l2-swap/styles.ts @@ -0,0 +1,92 @@ +import styled from 'styled-components'; +import { Button } from '@lidofinance/lido-ui'; +import Icons from 'assets/icons/l2-swap.svg'; + +export const Wrapper = styled.div` + margin-top: 16px; + position: relative; + display: flex; + align-items: center; + padding: ${({ theme }) => theme.spaceMap.lg}px; + border-radius: ${({ theme }) => theme.borderRadiusesMap.lg}px; + height: 160px; + gap: 10px; + overflow: hidden; + background-color: #07080c; + background: radial-gradient( + 138.42% 124.8% at 6.55% 17.29%, + #3c64b6 0%, + #2e1d7b 55.75%, + #142698 100% + ), + linear-gradient(90deg, #3487e5 0%, #006ae3 46.27%); + box-sizing: border-box; + + ${({ theme }) => theme.mediaQueries.md} { + gap: 6px; + padding: ${({ theme }) => theme.spaceMap.md}px; + } + + ${({ theme }) => theme.mediaQueries.sm} { + cursor: pointer; + padding: ${({ theme }) => theme.spaceMap.sm}px; + } +`; + +export const L2Icons = styled.img.attrs({ + src: Icons, + alt: '', +})` + position: relative; + display: block; + width: 44px; + height: 120px; +`; + +export const ContentWrap = styled.div` + width: 100%; + height: 100%; + box-sizing: border-box; +`; + +export const TextHeader = styled.div` + font-size: 14px; + line-height: 24px; + font-weight: 700; + color: #fff; +`; + +export const TextWrap = styled.div` + flex: 1 1 auto; + color: #fff; + line-height: 20px; + font-size: 12px; + font-weight: 400; + position: relative; +`; + +export const ButtonWrap = styled.div` + display: flex; + margin-top: 12px; +`; + +export const ButtonLinkWrap = styled.a` + display: block; + + ${({ theme }) => theme.mediaQueries.sm} { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + } +`; + +export const ButtonStyle = styled(Button)` + ${({ theme }) => theme.mediaQueries.sm} { + display: none; + } + padding: 7px 16px; + font-size: 12px; + line-height: 20px; +`; diff --git a/shared/components/tx-stage-modal/tx-stage-modal.tsx b/shared/components/tx-stage-modal/tx-stage-modal.tsx index 7c1c12f7f..54888c216 100644 --- a/shared/components/tx-stage-modal/tx-stage-modal.tsx +++ b/shared/components/tx-stage-modal/tx-stage-modal.tsx @@ -3,7 +3,7 @@ import { useConnectorInfo } from 'reef-knot/web3-react'; import { use1inchLinkProps } from 'features/home/hooks'; import { TxLinkEtherscan } from 'shared/components/tx-link-etherscan'; -import { ModalPoolBanners } from 'shared/banners'; +import { L2LowFee } from 'shared/banners'; import { TxStageModalShape } from './tx-stage-modal-shape'; import { ErrorMessage, formatBalance, formatBalanceString } from 'utils'; import { ModalProps } from '@lidofinance/lido-ui'; @@ -34,7 +34,7 @@ interface TxStageModalProps extends ModalProps { willReceiveAmountToken?: string; txHash?: string | null; balance?: BigNumber; - balanceToken?: string; + balanceToken?: 'stETH' | 'wstETH'; allowanceAmount?: BigNumber; failedText?: string | null; onRetry: React.MouseEventHandler; @@ -183,7 +183,7 @@ export const TxStageModal = memo((props: TxStageModalProps) => { } description={successText} - footer={} + footer={} /> ); } diff --git a/shared/forms/components/input-amount/input-amount.tsx b/shared/forms/components/input-amount/input-amount.tsx index 0075199bb..281f397dc 100644 --- a/shared/forms/components/input-amount/input-amount.tsx +++ b/shared/forms/components/input-amount/input-amount.tsx @@ -1,18 +1,32 @@ -import { forwardRef, useCallback, useEffect, useState } from 'react'; +import { + ChangeEvent, + ComponentProps, + forwardRef, + MouseEvent, + useCallback, + useEffect, + useState, +} from 'react'; import { BigNumber } from 'ethers'; + import { formatEther, parseEther } from '@ethersproject/units'; +import { MaxUint256 } from '@ethersproject/constants'; +import { Input } from '@lidofinance/lido-ui'; + import { InputNumber } from '../input-number'; import { InputDecoratorMaxButton } from '../input-decorator-max-button'; import { InputDecoratorLocked } from '../input-decorator-locked'; -import { MaxUint256 } from '@ethersproject/constants'; -import { Input } from '@lidofinance/lido-ui'; type InputAmountProps = { onChange?: (value: BigNumber | null) => void; value?: BigNumber | null; + onMaxClick?: ( + event: MouseEvent, + maxValue: BigNumber, + ) => void; maxValue?: BigNumber; isLocked?: boolean; -} & Omit, 'onChange' | 'value'>; +} & Omit, 'onChange' | 'value'>; const parseEtherSafe = (value: string) => { try { @@ -27,6 +41,7 @@ export const InputAmount = forwardRef( { onChange, value, + onMaxClick, rightDecorator, isLocked, maxValue, @@ -40,7 +55,7 @@ export const InputAmount = forwardRef( ); const handleChange = useCallback( - (e: React.ChangeEvent) => { + (e: ChangeEvent) => { // Support for devices where inputMode="decimal" showing keyboard with comma as decimal delimiter if (e.currentTarget.value.includes(',')) { e.currentTarget.value = e.currentTarget.value.replaceAll(',', '.'); @@ -86,7 +101,12 @@ export const InputAmount = forwardRef( }, [stringValue, value]); const handleClickMax = - onChange && maxValue?.gt(0) ? () => onChange(maxValue) : undefined; + onChange && maxValue?.gt(0) + ? (event: MouseEvent) => { + onChange(maxValue); + onMaxClick?.(event, maxValue); + } + : undefined; return ( ; + onClick?: MouseEventHandler; }; export const InputDecoratorMaxButton = ({