From d87807c084e4ad6e70616fbcc6f5adcb32031f71 Mon Sep 17 00:00:00 2001 From: Dmitrii Podlesnyi Date: Thu, 18 Jan 2024 15:19:08 +0400 Subject: [PATCH 1/3] feat: paraswap withdrawal aggregator --- .../withdrawals/hooks/useWithdrawalRates.ts | 80 ++++++++++++++++++- .../request/form/options/dex-options.tsx | 15 +++- .../request/form/options/options-picker.tsx | 2 + .../request/form/options/styles.ts | 8 ++ 4 files changed, 102 insertions(+), 3 deletions(-) diff --git a/features/withdrawals/hooks/useWithdrawalRates.ts b/features/withdrawals/hooks/useWithdrawalRates.ts index 7398aae24..b107b852c 100644 --- a/features/withdrawals/hooks/useWithdrawalRates.ts +++ b/features/withdrawals/hooks/useWithdrawalRates.ts @@ -3,7 +3,7 @@ import { useWatch } from 'react-hook-form'; import { BigNumber } from 'ethers'; import { Zero } from '@ethersproject/constants'; -import { TOKENS } from '@lido-sdk/constants'; +import { CHAINS, getTokenAddress, TOKENS } from '@lido-sdk/constants'; import { useLidoSWR } from '@lido-sdk/react'; import { useDebouncedValue } from 'shared/hooks/useDebouncedValue'; @@ -11,12 +11,18 @@ import { STRATEGY_LAZY } from 'utils/swrStrategies'; import { RequestFormInputType } from '../request/request-form-context'; import { getOpenOceanRate } from 'utils/get-open-ocean-rate'; +import { standardFetcher } from 'utils/standardFetcher'; type GetWithdrawalRateParams = { amount: BigNumber; token: TOKENS.STETH | TOKENS.WSTETH; }; +type RateCalculationResult = { + rate: number; + toReceive: BigNumber; +}; + type SingleWithdrawalRateResult = { name: string; rate: number | null; @@ -29,6 +35,20 @@ type GetRateType = ( type GetWithdrawalRateResult = SingleWithdrawalRateResult[]; +const RATE_PRECISION = 100000; +const RATE_PRECISION_BN = BigNumber.from(RATE_PRECISION); + +const calculateRateReceive = ( + amount: BigNumber, + src: BigNumber, + dest: BigNumber, +): RateCalculationResult => { + const _rate = dest.mul(RATE_PRECISION_BN).div(src); + const toReceive = amount.mul(dest).div(src); + const rate = _rate.toNumber() / RATE_PRECISION; + return { rate, toReceive }; +}; + const getOpenOceanWithdrawalRate: GetRateType = async ({ amount, token }) => { if (amount && amount.gt(Zero)) { try { @@ -49,10 +69,66 @@ const getOpenOceanWithdrawalRate: GetRateType = async ({ amount, token }) => { }; }; +type ParaSwapPriceResponsePartial = { + priceRoute: { + srcAmount: string; + destAmount: string; + }; +}; + +const getParaSwapRate: GetRateType = async ({ amount, token }) => { + let rateInfo: RateCalculationResult | null; + + try { + if (amount.isZero() || amount.isNegative()) { + return { + name: 'paraswap', + rate: 0, + toReceive: BigNumber.from(0), + }; + } + const capped_amount = amount; + const api = `https://apiv5.paraswap.io/prices`; + const query = new URLSearchParams({ + srcToken: getTokenAddress(CHAINS.Mainnet, token), + srcDecimals: '18', + destToken: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', + destDecimals: '18', + side: 'SELL', + excludeDirectContractMethods: 'true', + userAddress: '0x0000000000000000000000000000000000000000', + amount: capped_amount.toString(), + network: '1', + partner: 'lido', + }); + + const url = `${api}?${query.toString()}`; + const data: ParaSwapPriceResponsePartial = + await standardFetcher(url); + + rateInfo = calculateRateReceive( + amount, + BigNumber.from(data.priceRoute.srcAmount), + BigNumber.from(data.priceRoute.destAmount), + ); + } catch { + rateInfo = null; + } + + return { + name: 'paraswap', + rate: rateInfo?.rate ?? null, + toReceive: rateInfo?.toReceive ?? null, + }; +}; + const getWithdrawalRates = async ( params: GetWithdrawalRateParams, ): Promise => { - const rates = await Promise.all([getOpenOceanWithdrawalRate(params)]); + const rates = await Promise.all([ + getOpenOceanWithdrawalRate(params), + getParaSwapRate(params), + ]); if (rates.length > 1) { // sort by rate, then alphabetic diff --git a/features/withdrawals/request/form/options/dex-options.tsx b/features/withdrawals/request/form/options/dex-options.tsx index b6a88f452..61da23f91 100644 --- a/features/withdrawals/request/form/options/dex-options.tsx +++ b/features/withdrawals/request/form/options/dex-options.tsx @@ -1,5 +1,5 @@ import { BigNumber } from 'ethers'; -import { TOKENS } from '@lido-sdk/constants'; +import { CHAINS, getTokenAddress, TOKENS } from '@lido-sdk/constants'; import { useWithdrawalRates } from 'features/withdrawals/hooks/useWithdrawalRates'; import { FormatToken } from 'shared/formatters/format-token'; @@ -17,6 +17,7 @@ import { InlineLoaderSmall, DexOptionLoader, OpenOceanIcon, + ParaSwapIcon, } from './styles'; import { formatEther } from '@ethersproject/units'; import { OPEN_OCEAN_REFERRAL_ADDRESS } from 'config/external-links'; @@ -42,6 +43,18 @@ const dexInfo: { amount, )}#/ETH/${token}/ETH`, }, + paraswap: { + title: 'ParaSwap', + icon: , + onClickGoTo: () => { + trackMatomoEvent(MATOMO_CLICK_EVENTS_TYPES.withdrawalGoToParaswap); + }, + link: (amount, token) => + `https://app.paraswap.io/#/${getTokenAddress( + CHAINS.Mainnet, + token, + )}-ETH/${formatEther(amount)}?network=ethereum`, + }, }; type DexOptionProps = { diff --git a/features/withdrawals/request/form/options/options-picker.tsx b/features/withdrawals/request/form/options/options-picker.tsx index 33b4a0201..f45dd0546 100644 --- a/features/withdrawals/request/form/options/options-picker.tsx +++ b/features/withdrawals/request/form/options/options-picker.tsx @@ -16,6 +16,7 @@ import { OptionsPickerRow, OptionsPickerSubLabel, OpenOceanIcon, + ParaSwapIcon, } from './styles'; import { trackMatomoEvent, @@ -88,6 +89,7 @@ const DexButton: React.FC = ({ isActive, onClick }) => { Use aggregators + diff --git a/features/withdrawals/request/form/options/styles.ts b/features/withdrawals/request/form/options/styles.ts index ff0de1205..7291e0d99 100644 --- a/features/withdrawals/request/form/options/styles.ts +++ b/features/withdrawals/request/form/options/styles.ts @@ -5,6 +5,7 @@ import { FormatToken } from 'shared/formatters'; import Lido from 'assets/icons/lido.svg'; import OpenOcean from 'assets/icons/open-ocean.svg'; import ExternalLink from 'assets/icons/external-link-icon.svg'; +import Paraswap from 'assets/icons/paraswap-circle.svg'; // ICONS @@ -22,6 +23,13 @@ export const OpenOceanIcon = styled.img.attrs({ display: block; `; +export const ParaSwapIcon = styled.img.attrs({ + src: Paraswap, + alt: 'paraswap', +})` + display: block; +`; + export const OptionAmountRow = styled.div` display: flex; align-items: center; From d54b617dc8cf7354abf37cb836f800fa89db4f71 Mon Sep 17 00:00:00 2001 From: Dmitrii Podlesnyi Date: Fri, 19 Jan 2024 16:55:48 +0400 Subject: [PATCH 2/3] chore: paraswap link updated --- features/withdrawals/request/form/options/dex-options.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/features/withdrawals/request/form/options/dex-options.tsx b/features/withdrawals/request/form/options/dex-options.tsx index 61da23f91..ea13d6f0c 100644 --- a/features/withdrawals/request/form/options/dex-options.tsx +++ b/features/withdrawals/request/form/options/dex-options.tsx @@ -50,10 +50,12 @@ const dexInfo: { trackMatomoEvent(MATOMO_CLICK_EVENTS_TYPES.withdrawalGoToParaswap); }, link: (amount, token) => - `https://app.paraswap.io/#/${getTokenAddress( + `https://app.paraswap.io/referrer=Lido&takeSurplus=true#/${getTokenAddress( CHAINS.Mainnet, token, - )}-ETH/${formatEther(amount)}?network=ethereum`, + )}-0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE/${formatEther( + amount, + )}/SELL?network=ethereum`, }, }; From ee0be1ab20e95ded59d90dcc2e9b9a35034aa034 Mon Sep 17 00:00:00 2001 From: Dmitrii Podlesnyi Date: Fri, 19 Jan 2024 17:05:04 +0400 Subject: [PATCH 3/3] fix: paraswap link --- features/withdrawals/request/form/options/dex-options.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/withdrawals/request/form/options/dex-options.tsx b/features/withdrawals/request/form/options/dex-options.tsx index ea13d6f0c..bee78c753 100644 --- a/features/withdrawals/request/form/options/dex-options.tsx +++ b/features/withdrawals/request/form/options/dex-options.tsx @@ -50,7 +50,7 @@ const dexInfo: { trackMatomoEvent(MATOMO_CLICK_EVENTS_TYPES.withdrawalGoToParaswap); }, link: (amount, token) => - `https://app.paraswap.io/referrer=Lido&takeSurplus=true#/${getTokenAddress( + `https://app.paraswap.io/?referrer=Lido&takeSurplus=true#/${getTokenAddress( CHAINS.Mainnet, token, )}-0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE/${formatEther(