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 = ({