diff --git a/assets/icons/bebop.svg b/assets/icons/bebop.svg
new file mode 100644
index 000000000..1d451b365
--- /dev/null
+++ b/assets/icons/bebop.svg
@@ -0,0 +1,19 @@
+
diff --git a/config/matomoClickEvents.ts b/config/matomoClickEvents.ts
index 4d213a932..7384bf81d 100644
--- a/config/matomoClickEvents.ts
+++ b/config/matomoClickEvents.ts
@@ -49,6 +49,7 @@ export const enum MATOMO_CLICK_EVENTS_TYPES {
withdrawalOtherFactorsTooltipMode = 'withdrawalOtherFactorsTooltipMode',
withdrawalFAQtooltipEthAmount = 'withdrawalFAQtooltipEthAmount',
withdrawalGoTo1inch = 'withdrawalGoTo1inch',
+ withdrawalGoToBebop = 'withdrawalGoToBebop',
withdrawalGoToCowSwap = 'withdrawalGoToCowSwap',
withdrawalGoToParaswap = 'withdrawalGoToParaswap',
withdrawalGoToOpenOcean = 'withdrawalGoToOpenOcean',
@@ -279,6 +280,11 @@ export const MATOMO_CLICK_EVENTS: Record<
'Click on «Go to 1inch» in aggregators list on Request tab',
'eth_withdrawals_request_go_to_1inch',
],
+ [MATOMO_CLICK_EVENTS_TYPES.withdrawalGoToBebop]: [
+ 'Ethereum_Withdrawals_Widget',
+ 'Click on «Go to Bebop» in aggregators list on Request tab',
+ 'eth_withdrawals_request_go_to_1inch',
+ ],
[MATOMO_CLICK_EVENTS_TYPES.withdrawalGoToCowSwap]: [
'Ethereum_Withdrawals_Widget',
'Click on «Go to CowSwap» in aggregators list on Request tab',
diff --git a/features/withdrawals/request/withdrawal-rates/icons.tsx b/features/withdrawals/request/withdrawal-rates/icons.tsx
index e6343474f..a598f670d 100644
--- a/features/withdrawals/request/withdrawal-rates/icons.tsx
+++ b/features/withdrawals/request/withdrawal-rates/icons.tsx
@@ -2,6 +2,7 @@ import styled from 'styled-components';
import OpenOcean from 'assets/icons/open-ocean.svg';
import Paraswap from 'assets/icons/paraswap-circle.svg';
import Oneinch from 'assets/icons/oneinch-circle.svg';
+import Bebop from 'assets/icons/bebop.svg';
export const OpenOceanIcon = styled.img.attrs({
src: OpenOcean,
@@ -23,3 +24,10 @@ export const OneInchIcon = styled.img.attrs({
})`
display: block;
`;
+
+export const BebopIcon = styled.img.attrs({
+ src: Bebop,
+ alt: 'Bebop',
+})`
+ display: block;
+`;
diff --git a/features/withdrawals/request/withdrawal-rates/integrations.ts b/features/withdrawals/request/withdrawal-rates/integrations.ts
index a4db98ad6..18be27353 100644
--- a/features/withdrawals/request/withdrawal-rates/integrations.ts
+++ b/features/withdrawals/request/withdrawal-rates/integrations.ts
@@ -1,15 +1,17 @@
import { Zero } from '@ethersproject/constants';
import { getTokenAddress, CHAINS, TOKENS } from '@lido-sdk/constants';
import { BigNumber } from 'ethers';
+import { getAddress } from 'ethers/lib/utils.js';
import { formatEther } from '@ethersproject/units';
import { getOneInchRate } from 'utils/get-one-inch-rate';
+import { getBebopRate } from 'utils/get-bebop-rate';
import { getOpenOceanRate } from 'utils/get-open-ocean-rate';
import { standardFetcher } from 'utils/standardFetcher';
import { OPEN_OCEAN_REFERRAL_ADDRESS } from 'config/external-links';
import { MATOMO_CLICK_EVENTS_TYPES } from 'config/matomoClickEvents';
-import { OneInchIcon, OpenOceanIcon, ParaSwapIcon } from './icons';
+import { BebopIcon, OneInchIcon, OpenOceanIcon, ParaSwapIcon } from './icons';
import type {
DexWithdrawalApi,
@@ -124,6 +126,23 @@ const getOneInchWithdrawalRate: GetRateType = async (params) => {
};
};
+const getBebopWithdrawalRate: GetRateType = async ({ amount, token }) => {
+ try {
+ if (amount.gt(Zero)) {
+ return await getBebopRate(amount, token, 'ETH');
+ }
+ } catch (e) {
+ console.warn(
+ '[getOneInchWithdrawalRate] Failed to receive withdraw rate',
+ e,
+ );
+ }
+ return {
+ rate: null,
+ toReceive: null,
+ };
+};
+
const dexWithdrawalMap: DexWithdrawalIntegrationMap = {
'open-ocean': {
title: 'OpenOcean',
@@ -158,6 +177,16 @@ const dexWithdrawalMap: DexWithdrawalIntegrationMap = {
token == TOKENS.STETH ? 'stETH' : 'wstETH'
}/ETH?sourceTokenAmount=${formatEther(amount)}`,
},
+ bebop: {
+ title: 'Bebop',
+ icon: BebopIcon,
+ fetcher: getBebopWithdrawalRate,
+ matomoEvent: MATOMO_CLICK_EVENTS_TYPES.withdrawalGoToBebop,
+ link: (amount, token) =>
+ `https://bebop.xyz/trade?network=ethereum&buy=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE&sell=${getAddress(
+ getTokenAddress(CHAINS.Mainnet, token),
+ )}&sellAmounts=${formatEther(amount)}`,
+ },
} as const;
export const getDexConfig = (dexKey: DexWithdrawalApi) =>
diff --git a/features/withdrawals/request/withdrawal-rates/types.ts b/features/withdrawals/request/withdrawal-rates/types.ts
index d434b7ab2..5bc9458a9 100644
--- a/features/withdrawals/request/withdrawal-rates/types.ts
+++ b/features/withdrawals/request/withdrawal-rates/types.ts
@@ -19,7 +19,7 @@ export type SingleWithdrawalRateResult = {
toReceive: BigNumber | null;
};
-export type DexWithdrawalApi = 'paraswap' | 'open-ocean' | 'one-inch';
+export type DexWithdrawalApi = 'paraswap' | 'open-ocean' | 'one-inch' | 'bebop';
export type DexWithdrawalIntegration = {
title: string;
diff --git a/features/withdrawals/withdrawals-constants/index.ts b/features/withdrawals/withdrawals-constants/index.ts
index 037375acc..3c7f39a62 100644
--- a/features/withdrawals/withdrawals-constants/index.ts
+++ b/features/withdrawals/withdrawals-constants/index.ts
@@ -13,6 +13,6 @@ export const VALIDATION_CONTEXT_TIMEOUT = 4000;
export const ENABLED_WITHDRAWAL_DEXES: DexWithdrawalApi[] = [
'one-inch',
- 'open-ocean',
+ 'bebop',
'paraswap',
];
diff --git a/utils/get-bebop-rate.ts b/utils/get-bebop-rate.ts
new file mode 100644
index 000000000..79536b911
--- /dev/null
+++ b/utils/get-bebop-rate.ts
@@ -0,0 +1,81 @@
+import { CHAINS, TOKENS, getTokenAddress } from '@lido-sdk/constants';
+import { BigNumber } from 'ethers';
+import { standardFetcher } from './standardFetcher';
+import { ESTIMATE_ACCOUNT } from 'config';
+import { getAddress } from 'ethers/lib/utils.js';
+
+type BebopGetQuotePartial = {
+ routes: {
+ quote: {
+ buyTokens: Record<
+ string,
+ {
+ amount: string;
+ amountBeforeFee: string;
+ }
+ >;
+ sellTokens: Record<
+ string,
+ {
+ amount: string;
+ priceBeforeFee: number;
+ }
+ >;
+ };
+ }[];
+};
+
+type RateToken = TOKENS.STETH | TOKENS.WSTETH | 'ETH';
+
+type RateCalculationResult = { rate: number; toReceive: BigNumber };
+
+const getRateTokenAddress = (token: RateToken) =>
+ token === 'ETH'
+ ? '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE'
+ : getTokenAddress(CHAINS.Mainnet, token);
+
+export const getBebopRate = async (
+ amount: BigNumber,
+ fromToken: RateToken,
+ toToken: RateToken,
+): Promise => {
+ const basePath = 'https://api.bebop.xyz/router/ethereum/v1/quote';
+
+ const sell_tokens = getAddress(getRateTokenAddress(fromToken));
+ const buy_tokens = getAddress(getRateTokenAddress(toToken));
+
+ const params = new URLSearchParams({
+ sell_tokens,
+ buy_tokens,
+ taker_address: ESTIMATE_ACCOUNT,
+ sell_amounts: amount.toString(),
+ approval_type: 'Standard',
+ });
+
+ const data = await standardFetcher(
+ `${basePath}/?${params.toString()}`,
+ );
+
+ const bestRoute = data.routes.toSorted(
+ (r1, r2) =>
+ r2.quote.sellTokens[sell_tokens].priceBeforeFee -
+ r1.quote.sellTokens[sell_tokens].priceBeforeFee,
+ )[0];
+
+ if (
+ bestRoute &&
+ bestRoute.quote.sellTokens[sell_tokens] &&
+ bestRoute.quote.buyTokens[buy_tokens]
+ ) {
+ const rate = data.routes[0].quote.sellTokens[sell_tokens].priceBeforeFee;
+
+ const toAmount = BigNumber.from(
+ data.routes[0].quote.buyTokens[buy_tokens].amountBeforeFee,
+ );
+ return {
+ rate,
+ toReceive: toAmount,
+ };
+ }
+ throw new Error('[getBebopRate] Could not get quote, invalid response body');
+};