Skip to content

Commit

Permalink
Merge pull request #244 from lidofinance/develop
Browse files Browse the repository at this point in the history
Develop to main
  • Loading branch information
itaven authored Feb 21, 2024
2 parents ff72eb3 + 37ebbed commit 82fa7d2
Show file tree
Hide file tree
Showing 31 changed files with 759 additions and 463 deletions.
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ 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
Expand Down
6 changes: 6 additions & 0 deletions config/matomoClickEvents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const enum MATOMO_CLICK_EVENTS_TYPES {
clickExploreDeFi = 'clickExploreDeFi',
// / page
openOceanDiscount = 'openOceanDiscount',
oneInchDiscount = 'oneInchDiscount',
viewEtherscanOnStakePage = 'viewEtherscanOnStakePage',
l2BannerStake = 'l2BannerStake',
l2LowFeeStake = 'l2LowFeeStake',
Expand Down Expand Up @@ -93,6 +94,11 @@ export const MATOMO_CLICK_EVENTS: Record<
'Push "Get discount" on OpenOcean banner on widget',
'eth_widget_openocean_discount',
],
[MATOMO_CLICK_EVENTS_TYPES.oneInchDiscount]: [
'Ethereum_Staking_Widget',
'Push "Get discount" on 1inch banner on widget',
'eth_widget_oneinch_discount',
],
[MATOMO_CLICK_EVENTS_TYPES.viewEtherscanOnStakePage]: [
'Ethereum_Staking_Widget',
'Push «View on Etherscan» on the right side of Lido Statistics',
Expand Down
4 changes: 4 additions & 0 deletions config/stake.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { BigNumber } from 'ethers';
import dynamics from './dynamics';
import { IPFS_REFERRAL_ADDRESS } from './ipfs';
import { AddressZero } from '@ethersproject/constants';
import { StakeSwapDiscountIntegrationKey } from 'features/stake/swap-discount-banner';

export const PRECISION = 10 ** 6;

Expand All @@ -22,3 +23,6 @@ export const STAKE_GASLIMIT_FALLBACK = BigNumber.from(
export const STAKE_FALLBACK_REFERRAL_ADDRESS = dynamics.ipfsMode
? IPFS_REFERRAL_ADDRESS
: AddressZero;

export const STAKE_SWAP_INTEGRATION: StakeSwapDiscountIntegrationKey =
'one-inch';
13 changes: 3 additions & 10 deletions features/stake/hooks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@ import { useMemo } from 'react';
import { isDesktop } from 'react-device-detect';
import { useConnectorInfo } from 'reef-knot/web3-react';

const ONE_INCH_URL = 'https://app.1inch.io/#/1/swap/ETH/steth';
const LEDGER_LIVE_ONE_INCH_DESKTOP_DEEPLINK = 'ledgerlive://discover/1inch-lld';
const LEDGER_LIVE_ONE_INCH_MOBILE_DEEPLINK = 'ledgerlive://discover/1inch-llm';

export const use1inchLinkProps = () => {
export const use1inchDeepLinkProps = () => {
const { isLedgerLive } = useConnectorInfo();

const linkProps = useMemo(() => {
return useMemo(() => {
if (isLedgerLive) {
const href = isDesktop
? LEDGER_LIVE_ONE_INCH_DESKTOP_DEEPLINK
Expand All @@ -20,13 +19,7 @@ export const use1inchLinkProps = () => {
target: '_self',
};
} else {
return {
href: ONE_INCH_URL,
target: '_blank',
rel: 'noopener noreferrer',
};
return {};
}
}, [isLedgerLive]);

return linkProps;
};
1 change: 1 addition & 0 deletions features/stake/swap-discount-banner/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { SwapDiscountBanner } from './swap-discount-banner';
export type { StakeSwapDiscountIntegrationKey } from './types';
68 changes: 68 additions & 0 deletions features/stake/swap-discount-banner/integrations.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { TOKENS } from '@lido-sdk/constants';
import { getOpenOceanRate } from 'utils/get-open-ocean-rate';
import {
StakeSwapDiscountIntegrationKey,
StakeSwapDiscountIntegrationMap,
} from './types';
import { OpenOceanIcon, OneInchIcon, OverlayLink } from './styles';
import { parseEther } from '@ethersproject/units';
import { OPEN_OCEAN_REFERRAL_ADDRESS } from 'config/external-links';
import { MATOMO_CLICK_EVENTS } from 'config/matomoClickEvents';
import { getOneInchRate } from 'utils/get-one-inch-rate';
import { use1inchDeepLinkProps } from 'features/stake/hooks';

const DEFAULT_AMOUNT = parseEther('1');

const STAKE_SWAP_INTEGRATION_CONFIG: StakeSwapDiscountIntegrationMap = {
'open-ocean': {
title: 'OpenOcean',
async getRate() {
const { rate } = await getOpenOceanRate(
DEFAULT_AMOUNT,
'ETH',
TOKENS.STETH,
);
return rate;
},
BannerText({ discountPercent }) {
return (
<>
Get a <b>{discountPercent.toFixed(2)}% discount</b> by swapping to
stETH&nbsp;on the OpenOcean platform
</>
);
},
Icon: OpenOceanIcon,
linkHref: `https://app.openocean.finance/classic?referrer=${OPEN_OCEAN_REFERRAL_ADDRESS}#/ETH/ETH/STETH`,
matomoEvent: MATOMO_CLICK_EVENTS.openOceanDiscount,
},
'one-inch': {
title: '1inch',
async getRate() {
const { rate } = await getOneInchRate({ token: 'ETH' });
return rate;
},
BannerText({ discountPercent }) {
return (
<>
Get a <b>{discountPercent.toFixed(2)}% discount</b> by swapping to
stETH&nbsp;on the 1inch platform
</>
);
},
Icon: OneInchIcon,
linkHref: `https://app.1inch.io/#/1/simple/swap/ETH/stETH`,
matomoEvent: MATOMO_CLICK_EVENTS.oneInchDiscount,
CustomLink({ children, ...props }) {
const customProps = use1inchDeepLinkProps();
return (
<OverlayLink {...props} {...customProps}>
{children}
</OverlayLink>
);
},
},
};

export const getSwapIntegration = (key: StakeSwapDiscountIntegrationKey) =>
STAKE_SWAP_INTEGRATION_CONFIG[key];
12 changes: 11 additions & 1 deletion features/stake/swap-discount-banner/styles.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import styled from 'styled-components';
import BgSrc from 'assets/icons/swap-banner-bg.svg';
import OpenOcean from 'assets/icons/open-ocean.svg';
import OneInch from 'assets/icons/oneinch-circle.svg';

export const Wrap = styled.div`
position: relative;
Expand Down Expand Up @@ -42,13 +43,22 @@ export const OverlayLink = styled.a`

export const OpenOceanIcon = styled.img.attrs({
src: OpenOcean,
alt: 'openOcean',
alt: 'OpenOcean',
})`
width: 40px;
height: 40px;
display: block;
`;

export const OneInchIcon = styled.img.attrs({
src: OneInch,
alt: '1inch',
})`
display: block;
width: 40px;
height: 40px;
`;

export const TextWrap = styled.p`
flex: 1 1 auto;
color: #fff;
Expand Down
91 changes: 22 additions & 69 deletions features/stake/swap-discount-banner/swap-discount-banner.tsx
Original file line number Diff line number Diff line change
@@ -1,90 +1,43 @@
import { Button } from '@lidofinance/lido-ui';
import { trackEvent } from '@lidofinance/analytics-matomo';

import { MATOMO_CLICK_EVENTS } from 'config';
import { OPEN_OCEAN_REFERRAL_ADDRESS } from 'config/external-links';
import { STRATEGY_LAZY } from 'utils/swrStrategies';
import { getOpenOceanRate } from 'utils/get-open-ocean-rate';
import { parseEther } from '@ethersproject/units';
import { TOKENS } from '@lido-sdk/constants';
import { useLidoSWR } from '@lido-sdk/react';
import { enableQaHelpers } from 'utils';

import { Wrap, TextWrap, OpenOceanIcon, OverlayLink } from './styles';

const SWAP_URL = `https://app.openocean.finance/classic?referrer=${OPEN_OCEAN_REFERRAL_ADDRESS}#/ETH/ETH/STETH`;
const DISCOUNT_THRESHOLD = 1.004;
const DEFAULT_AMOUNT = parseEther('1');
const MOCK_LS_KEY = 'mock-qa-helpers-discount-rate';

type FetchRateResult = {
rate: number;
shouldShowDiscount: boolean;
discountPercent: number;
};

const calculateDiscountState = (rate: number): FetchRateResult => ({
rate,
shouldShowDiscount: rate > DISCOUNT_THRESHOLD,
discountPercent: (1 - 1 / rate) * 100,
});

// we show banner if STETH is considerably cheaper to get on dex than staking
// ETH -> stETH rate > THRESHOLD
const fetchRate = async (): Promise<FetchRateResult> => {
const { rate } = await getOpenOceanRate(DEFAULT_AMOUNT, 'ETH', TOKENS.STETH);
return calculateDiscountState(rate);
};

const linkClickHandler = () =>
trackEvent(...MATOMO_CLICK_EVENTS.openOceanDiscount);

if (enableQaHelpers && typeof window !== 'undefined') {
(window as any).setMockDiscountRate = (rate?: number) =>
rate === undefined
? localStorage.removeItem(MOCK_LS_KEY)
: localStorage.setItem(MOCK_LS_KEY, rate.toString());
}

const getData = (data: FetchRateResult | undefined) => {
if (!enableQaHelpers || typeof window == 'undefined') return data;
const mock = localStorage.getItem(MOCK_LS_KEY);
if (mock) {
return calculateDiscountState(parseFloat(mock));
}
return data;
};
import { useSwapDiscount } from './use-swap-discount';
import { Wrap, TextWrap, OverlayLink } from './styles';

export const SwapDiscountBanner = ({ children }: React.PropsWithChildren) => {
const swr = useLidoSWR<FetchRateResult>(
['swr:open-ocean-rate'],
fetchRate,
STRATEGY_LAZY,
);

const data = getData(swr.data);
const { data, initialLoading } = useSwapDiscount();

if (swr.initialLoading) return null;
if (initialLoading) return null;

if (!data?.shouldShowDiscount) return <>{children}</>;
if (!data || !data.shouldShowDiscount) return <>{children}</>;

const {
BannerText,
Icon,
discountPercent,
matomoEvent,
linkHref,
CustomLink,
} = data;
const Link = CustomLink ?? OverlayLink;
return (
<Wrap>
<OpenOceanIcon />
<Icon />
<TextWrap>
Get a <b>{data?.discountPercent.toFixed(2)}% discount</b> by swapping to
stETH&nbsp;on the OpenOcean platform
<BannerText discountPercent={discountPercent} />
</TextWrap>
<OverlayLink
<Link
target="_blank"
rel="noreferrer"
href={SWAP_URL}
onClick={linkClickHandler}
href={linkHref}
onClick={() => {
trackEvent(...matomoEvent);
}}
>
<Button fullwidth size="xs">
Get discount
</Button>
</OverlayLink>
</Link>
</Wrap>
);
};
24 changes: 24 additions & 0 deletions features/stake/swap-discount-banner/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { MatomoEventType } from '@lidofinance/analytics-matomo';

export type StakeSwapDiscountIntegrationKey = 'open-ocean' | 'one-inch';

export type StakeSwapDiscountIntegrationValue = {
title: string;
getRate: () => Promise<number>;
linkHref: string;
CustomLink?: React.FC<React.PropsWithoutRef<React.ComponentProps<'a'>>>;
matomoEvent: MatomoEventType;
BannerText: React.FC<{ discountPercent: number }>;
Icon: React.FC;
};

export type StakeSwapDiscountIntegrationMap = Record<
StakeSwapDiscountIntegrationKey,
StakeSwapDiscountIntegrationValue
>;

export type FetchRateResult = {
rate: number;
shouldShowDiscount: boolean;
discountPercent: number;
};
60 changes: 60 additions & 0 deletions features/stake/swap-discount-banner/use-swap-discount.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { STRATEGY_LAZY } from 'utils/swrStrategies';
import { useLidoSWR } from '@lido-sdk/react';
import { enableQaHelpers } from 'utils';
import type {
FetchRateResult,
StakeSwapDiscountIntegrationKey,
StakeSwapDiscountIntegrationValue,
} from './types';
import { STAKE_SWAP_INTEGRATION } from 'config';
import { getSwapIntegration } from './integrations';

const DISCOUNT_THRESHOLD = 1.004;
const MOCK_LS_KEY = 'mock-qa-helpers-discount-rate';

if (enableQaHelpers && typeof window !== 'undefined') {
(window as any).setMockDiscountRate = (rate?: number) =>
rate === undefined
? localStorage.removeItem(MOCK_LS_KEY)
: localStorage.setItem(MOCK_LS_KEY, rate.toString());
}

// we show banner if STETH is considerably cheaper to get on dex than staking
// ETH -> stETH rate > THRESHOLD
const fetchRate = async (
_: string,
integrationKey: StakeSwapDiscountIntegrationKey,
): Promise<FetchRateResult & StakeSwapDiscountIntegrationValue> => {
const integration = getSwapIntegration(integrationKey);
let rate: number;
const mock = localStorage.getItem(MOCK_LS_KEY);
if (enableQaHelpers && mock) {
rate = parseFloat(mock);
} else {
rate = await integration.getRate();
}
return {
...integration,
rate,
shouldShowDiscount: rate > DISCOUNT_THRESHOLD,
discountPercent: (1 - 1 / rate) * 100,
};
};

export const useSwapDiscount = () => {
return useLidoSWR(
['swr:swap-discount-rate', STAKE_SWAP_INTEGRATION],
// @ts-expect-error useLidoSWR has broken fetcher-key type signature
fetchRate,
{
...STRATEGY_LAZY,
onError(error, key) {
console.warn(
`[useSwapDiscount] Error fetching ETH->Steth:`,
key,
error,
);
},
},
);
};
File renamed without changes.
Loading

0 comments on commit 82fa7d2

Please sign in to comment.