Skip to content

Commit

Permalink
Merge pull request #465 from soroswap/feat/SDEX-quote-on-swap
Browse files Browse the repository at this point in the history
✨SDEX quote on swap
  • Loading branch information
chopan123 authored Jul 26, 2024
2 parents 7bc06cd + fe6e3eb commit 3db2b70
Show file tree
Hide file tree
Showing 11 changed files with 619 additions and 130 deletions.
21 changes: 17 additions & 4 deletions cypress/e2e/flows.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ describe('Bridge flow', () => {
});

// Swap flow
describe('Select tokens & input ammount', () => {
describe('Select tokens & input amount', () => {
it('should navigate to swap', () => {
cy.visit('/swap');
cy.contains('You sell');
Expand All @@ -38,7 +38,7 @@ describe('Select tokens & input ammount', () => {
cy.get('[data-testid="token-search-input"]').type('xlm');
cy.get('[data-testid="currency__list__XLM"]').click();
});
it('should type an input ammount & expect for a token out', () => {
it('should type an input amount & wait for output amount', () => {
cy.visit('/swap');
//Select input asset
cy.get('[data-testid="swap__input__panel"]').within(() => {
Expand All @@ -60,7 +60,7 @@ describe('Select tokens & input ammount', () => {
//await for calcs
cy.wait(5000);

//Get the output ammount
//Get the output amount
cy.get('[data-testid="swap__output__panel"]').within(() => {
cy.get('.token-amount-input').invoke('val').as('outputAmount');
});
Expand Down Expand Up @@ -90,7 +90,7 @@ describe('Select tokens & input ammount', () => {
//await for calcs
cy.wait(2500);

//Get the output ammount
//Get the output amount
cy.get('[data-testid="swap__output__panel"]').within(() => {
cy.get('.token-amount-input').invoke('val').as('outputAmount');
});
Expand All @@ -111,6 +111,19 @@ describe('Select tokens & input ammount', () => {
cy.get('@priceImpact').should('exist');
cy.get('@expectedOutput').should('exist');
cy.get('@path').should('exist');
cy.contains('Price Impact')
cy.contains('Expected output')
cy.contains('Path')

cy.get('[data-testid="swap__details__priceImpact"]').as('priceImpact')
cy.get('[data-testid="swap__details__expectedOutput"]').as('expectedOutput')
cy.get('[data-testid="swap__details__path"]').as('path')
cy.get('[data-testid="swap__details__platform"]').as('platform')

cy.get('@priceImpact').should('exist')
cy.get('@expectedOutput').should('exist')
cy.get('@path').should('exist')
cy.get('@platform').should('exist')

cy.get('@priceImpact').contains('%');
cy.get('@expectedOutput').contains('XLM');
Expand Down
56 changes: 35 additions & 21 deletions src/components/Swap/AdvancedSwapDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { useAllTokens } from 'hooks/tokens/useAllTokens';
import { findToken } from 'hooks/tokens/useToken';
import React, { useEffect, useState } from 'react';
import { Percent } from 'soroswap-router-sdk';
import { InterfaceTrade } from 'state/routing/types';
import { InterfaceTrade, PlatformType } from 'state/routing/types';

export const PathBox = styled(Box)`
display: flex;
Expand Down Expand Up @@ -47,7 +47,7 @@ export function TextWithLoadingPlaceholder({
);
}

export const formattedPriceImpact = (priceImpact: Percent | undefined) => {
export const formattedPriceImpact = (priceImpact: Percent | Number | undefined) => {
if (!priceImpact) return 0;

const value = priceImpact?.toFixed(3);
Expand Down Expand Up @@ -82,23 +82,29 @@ export function AdvancedSwapDetails({
useEffect(() => {
(async () => {
if (!trade?.path || isLoading) return;

setPathTokensIsLoading(true);

const promises = trade.path.map(async (contract) => {
const asset = await findToken(contract, tokensAsMap, sorobanContext);
const code = asset?.code == 'native' ? 'XLM' : asset?.code;
return code;
});

const results = await Promise.allSettled(promises);

const fulfilledValues = results
.filter((result) => result.status === 'fulfilled' && result.value)
.map((result) => (result.status === 'fulfilled' && result.value ? result.value : ''));

setPathArray(fulfilledValues);
setPathTokensIsLoading(false);
if (trade.platform == PlatformType.ROUTER) {
setPathTokensIsLoading(true);
const promises = trade.path.map(async (contract) => {
const asset = await findToken(contract, tokensAsMap, sorobanContext);
const code = asset?.code == 'native' ? 'XLM' : asset?.code;
return code;
});
const results = await Promise.allSettled(promises);

const fulfilledValues = results
.filter((result) => result.status === 'fulfilled' && result.value)
.map((result) => (result.status === 'fulfilled' && result.value ? result.value : ''));
setPathArray(fulfilledValues);
setPathTokensIsLoading(false);
} else if (trade.platform == PlatformType.STELLAR_CLASSIC) {
setPathTokensIsLoading(true);
const codes = trade.path.map((address) => {
if (address == 'native') return 'XLM';
return address.split(':')[0];
});
setPathArray(codes);
setPathTokensIsLoading(false);
}
})();
}, [trade?.path, isLoading, sorobanContext]);

Expand All @@ -108,12 +114,12 @@ export function AdvancedSwapDetails({
{networkFees != 0 && (
<RowBetween>
<MouseoverTooltip
title={'The fee paid to miners who process your transaction. This must be paid in XLM.'}
title={'The fee paid to process your transaction. This must be paid always in XLM.'}
>
<BodySmall color="textSecondary">Network fee</BodySmall>
</MouseoverTooltip>
<TextWithLoadingPlaceholder syncing={syncing} width={50}>
<BodySmall>~{networkFees} XLM</BodySmall>
<BodySmall display={'flex'}>~{networkFees} XLM</BodySmall>
</TextWithLoadingPlaceholder>
</RowBetween>
)}
Expand Down Expand Up @@ -177,6 +183,14 @@ export function AdvancedSwapDetails({
</TextWithLoadingPlaceholder>
</RowBetween>
}
{trade?.platform && (
<RowBetween>
<MouseoverTooltip title={'The platform where the swap will be made.'}>
<BodySmall color="textSecondary">Platform</BodySmall>
</MouseoverTooltip>
<BodySmall data-testid="swap__details__platform">{trade.platform}</BodySmall>
</RowBetween>
)}
</Column>
);
}
50 changes: 32 additions & 18 deletions src/components/Swap/SwapModalFooter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import useGetReservesByPair from 'hooks/useGetReservesByPair';
import { getSwapAmounts } from 'hooks/useSwapCallback';
import React, { ReactNode, useEffect, useState } from 'react';
import { AlertTriangle, ChevronRight } from 'react-feather';
import { InterfaceTrade, TradeType } from 'state/routing/types';
import { InterfaceTrade, PlatformType, TradeType } from 'state/routing/types';
import { PathBox, TextWithLoadingPlaceholder, formattedPriceImpact } from './AdvancedSwapDetails';
import { Label } from './SwapModalHeaderAmount';
import { getExpectedAmountOfOne } from './TradePrice';
Expand Down Expand Up @@ -107,23 +107,29 @@ export default function SwapModalFooter({
useEffect(() => {
(async () => {
if (!trade?.path || isLoading) return;
if (trade.platform == PlatformType.ROUTER) {
setPathTokensIsLoading(true);
const promises = trade.path.map(async (contract) => {
const asset = await findToken(contract, tokensAsMap, sorobanContext);
const code = asset?.code == 'native' ? 'XLM' : asset?.code;
return code;
});
const results = await Promise.allSettled(promises);

setPathTokensIsLoading(true);

const promises = trade.path.map(async (contract) => {
const asset = await findToken(contract, tokensAsMap, sorobanContext);
const code = asset?.code == 'native' ? 'XLM' : asset?.code;
return code;
});

const results = await Promise.allSettled(promises);

const fulfilledValues = results
.filter((result) => result.status === 'fulfilled' && result.value)
.map((result) => (result.status === 'fulfilled' && result.value ? result.value : ''));

setPathArray(fulfilledValues);
setPathTokensIsLoading(false);
const fulfilledValues = results
.filter((result) => result.status === 'fulfilled' && result.value)
.map((result) => (result.status === 'fulfilled' && result.value ? result.value : ''));
setPathArray(fulfilledValues);
setPathTokensIsLoading(false);
} else if (trade.platform == PlatformType.STELLAR_CLASSIC) {
setPathTokensIsLoading(true);
const codes = trade.path.map((address) => {
if (address == "native") return "XLM"
return address.split(":")[0]
})
setPathArray(codes);
setPathTokensIsLoading(false);
}
})();
}, [trade?.path, isLoading, sorobanContext]);

Expand All @@ -148,7 +154,7 @@ export default function SwapModalFooter({
>
<Label cursor="help">Network fees</Label>
</MouseoverTooltip>
<DetailRowValue>~{networkFees} XML</DetailRowValue>
<DetailRowValue display={'flex'}>~{networkFees}{' '}XML</DetailRowValue>
{/* <MouseoverTooltip placement="right" title={'<GasBreakdownTooltip trade={trade} />'}>
</MouseoverTooltip> */}
</Row>
Expand Down Expand Up @@ -220,6 +226,14 @@ export default function SwapModalFooter({
</PathBox>
</TextWithLoadingPlaceholder>
</RowBetween>
{trade?.platform && (
<RowBetween>
<MouseoverTooltip title={'The platform where the swap will be made.'}>
<Label>Platform</Label>
</MouseoverTooltip>
<BodySmall data-testid="swap__details__platform">{trade.platform}</BodySmall>
</RowBetween>
)}
</DetailsContainer>
{showAcceptChanges ? (
<SwapShowAcceptChanges data-testid="show-accept-changes">
Expand Down
107 changes: 92 additions & 15 deletions src/functions/generateRoute.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,45 @@
import { useSorobanReact } from '@soroban-react/core';
import { SorobanContextType, useSorobanReact } from '@soroban-react/core';
import { AppContext } from 'contexts';
import { useFactory } from 'hooks';
import { useAggregator } from 'hooks/useAggregator';
import { useContext, useMemo } from 'react';
import { fetchAllPhoenixPairs, fetchAllSoroswapPairs } from 'services/pairs';
import { CurrencyAmount, Networks, Protocols, Router, Token, TradeType } from 'soroswap-router-sdk';
import {
Currency,
CurrencyAmount,
Networks,
Percent,
Protocols,
Route,
Router,
Token,
TradeType,
} from 'soroswap-router-sdk';
import { TokenType } from 'interfaces';
import { getBestPath, getHorizonBestPath } from 'helpers/horizon/getHorizonPath';
import { PlatformType } from 'state/routing/types';
import { CurrencyAmount as AmountAsset } from 'interfaces';
import { DexDistribution } from 'helpers/aggregator';

export interface BuildTradeRoute {
amountCurrency: AmountAsset | CurrencyAmount<Currency>;
quoteCurrency: AmountAsset | CurrencyAmount<Currency>;
tradeType: TradeType;
trade: {
amountIn?: string;
amountOut?: string;
amountOutMin?: string;
amountInMax?: string;
distribution?: DexDistribution[];
path: string[];
};
priceImpact: Percent | Number;
platform: PlatformType;
}

export interface GenerateRouteProps {
amountTokenAddress: string;
quoteTokenAddress: string;
amountAsset: AmountAsset;
quoteAsset: TokenType;
amount: string;
tradeType: TradeType;
}
Expand Down Expand Up @@ -69,23 +100,69 @@ export const useRouterSDK = () => {
};

const generateRoute = async ({
amountTokenAddress,
quoteTokenAddress,
amountAsset,
quoteAsset,
amount,
tradeType,
}: GenerateRouteProps) => {
if (!factory) throw new Error('Factory address not found');
const currencyAmount = fromAddressAndAmountToCurrencyAmount(amountTokenAddress, amount);
const quoteCurrency = fromAddressToToken(quoteTokenAddress);

const currencyAmount = fromAddressAndAmountToCurrencyAmount(
amountAsset.currency.contract,
amount,
);
const quoteCurrency = fromAddressToToken(quoteAsset.contract);

const horizonProps = {
assetFrom: amountAsset.currency,
assetTo: quoteAsset,
amount,
tradeType,
};

const horizonPath = await getHorizonBestPath(
horizonProps,
sorobanContext,
) as BuildTradeRoute;

let sorobanPath: BuildTradeRoute;
if (isAggregator) {
// console.log('Returning routeSplit');
// console.log(await router.routeSplit(currencyAmount, quoteCurrency, tradeType));
return router.routeSplit(currencyAmount, quoteCurrency, tradeType);
sorobanPath = (await router.routeSplit(currencyAmount, quoteCurrency, tradeType).then((response)=>{
if(!response) return undefined;
const result = {
...response,
platform: PlatformType.ROUTER,
};
return result;
}
)) as BuildTradeRoute;
} else {
sorobanPath = (await router.route(
currencyAmount,
quoteCurrency,
tradeType,
factory,
sorobanContext as SorobanContextType,
).then((response)=>{
if(!response) return undefined;
const result = {
...response,
platform: PlatformType.ROUTER,
};
return result;
}
)) as BuildTradeRoute;
}

// console.log('returning route');
return router.route(currencyAmount, quoteCurrency, tradeType, factory, sorobanContext as any);
const bestPath = getBestPath(horizonPath, sorobanPath, tradeType);
// .then((res) => {
// if (!res) return;
// const response = {
// ...res,
// platform: PlatformType.ROUTER,
// };
// return response;
// });

return bestPath;
};

return { generateRoute, resetRouterSdkCache, maxHops };
Expand Down
Loading

0 comments on commit 3db2b70

Please sign in to comment.