diff --git a/src/components/Swap/AdvancedSwapDetails.tsx b/src/components/Swap/AdvancedSwapDetails.tsx index 6268655f..546388d1 100644 --- a/src/components/Swap/AdvancedSwapDetails.tsx +++ b/src/components/Swap/AdvancedSwapDetails.tsx @@ -1,7 +1,6 @@ -import { BodySmall } from 'components/Text'; +import React from 'react'; +import { BodySmall, } from 'components/Text'; import { Box, styled } from 'soroswap-ui'; -import { ChevronRight } from '@mui/icons-material'; -import { useSorobanReact } from '@soroban-react/core'; import Column from 'components/Column'; import { LoadingRows } from 'components/Loader/styled'; import CurrencyLogo from 'components/Logo/CurrencyLogo'; @@ -10,19 +9,11 @@ import { Separator } from 'components/SearchModal/styleds'; import { MouseoverTooltip } from 'components/Tooltip'; import { formatTokenAmount } from 'helpers/format'; 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, PlatformType } from 'state/routing/types'; +import SwapPathComponent from './SwapPathComponent'; +import { InterfaceTrade } from 'state/routing/types'; -export const PathBox = styled(Box)` - display: flex; - align-items: center; - justify-content: center; - flex-direction: row; -`; - -interface AdvancedSwapDetailsProps { +export interface AdvancedSwapDetailsProps { trade: InterfaceTrade | undefined; allowedSlippage: number; syncing?: boolean; @@ -63,6 +54,13 @@ export const formattedPriceImpact = (priceImpact: Percent | Number | undefined) return `~${priceImpact?.toFixed(2)}%`; }; +export const FormattedProtocolName = (protocol: string) => { + return protocol.charAt(0).toUpperCase() + protocol.slice(1); +} +export const calculatePercentage = (parts: number, totalParts: number) => { + return (parts / totalParts) * 100; +} + export function AdvancedSwapDetails({ trade, allowedSlippage, @@ -72,42 +70,8 @@ export function AdvancedSwapDetails({ // const { chainId } = useWeb3React() // const nativeCurrency = useNativeCurrency(chainId) // const txCount = getTransactionCount(trade) - const sorobanContext = useSorobanReact(); const { tokensAsMap, isLoading } = useAllTokens(); - const [pathArray, setPathArray] = useState([]); - - const [pathTokensIsLoading, setPathTokensIsLoading] = useState(false); - - 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); - - 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]); - return ( @@ -160,29 +124,7 @@ export function AdvancedSwapDetails({ - { - - - - Path - - - - - {pathArray?.map((contract, index) => ( - - {contract} - {index !== pathArray.length - 1 && } - - ))} - - - - } + {trade?.platform && ( diff --git a/src/components/Swap/SwapModalFooter.tsx b/src/components/Swap/SwapModalFooter.tsx index 737c6ed6..0818ea14 100644 --- a/src/components/Swap/SwapModalFooter.tsx +++ b/src/components/Swap/SwapModalFooter.tsx @@ -1,25 +1,20 @@ -import { CircularProgress, styled, useTheme } from 'soroswap-ui'; -import { useSorobanReact } from '@soroban-react/core'; -import BigNumber from 'bignumber.js'; +import { styled, useTheme } from 'soroswap-ui'; import { ButtonError, SmallButtonPrimary } from 'components/Buttons/Button'; import Column from 'components/Column'; import CurrencyLogo from 'components/Logo/CurrencyLogo'; import Row, { AutoRow, RowBetween, RowFixed } from 'components/Row'; import { BodySmall, HeadlineSmall, SubHeaderSmall } from 'components/Text'; import { MouseoverTooltip } from 'components/Tooltip'; -import { getPriceImpactNew2 } from 'functions/getPriceImpact'; -import { formatTokenAmount, twoDecimalsPercentage } from 'helpers/format'; -import { useAllTokens } from 'hooks/tokens/useAllTokens'; -import { findToken } from 'hooks/tokens/useToken'; -import useGetReservesByPair from 'hooks/useGetReservesByPair'; +import { formatTokenAmount } from 'helpers/format'; import { getSwapAmounts } from 'hooks/useSwapCallback'; -import React, { ReactNode, useEffect, useState } from 'react'; -import { AlertTriangle, ChevronRight } from 'react-feather'; -import { InterfaceTrade, PlatformType, TradeType } from 'state/routing/types'; -import { PathBox, TextWithLoadingPlaceholder, formattedPriceImpact } from './AdvancedSwapDetails'; +import React, { ReactNode } from 'react'; +import { AlertTriangle } from 'react-feather'; +import { InterfaceTrade, TradeType } from 'state/routing/types'; +import { formattedPriceImpact } from './AdvancedSwapDetails'; import { Label } from './SwapModalHeaderAmount'; import { getExpectedAmountOfOne } from './TradePrice'; import { SwapCallbackError, SwapShowAcceptChanges } from './styleds'; +import SwapPathComponent from './SwapPathComponent'; const DetailsContainer = styled(Column)` padding: 0 8px; @@ -77,13 +72,10 @@ export default function SwapModalFooter({ // const routes = isClassicTrade(trade) ? getRoutingDiagramEntries(trade) : undefined // const { chainId } = useWeb3React() // const nativeCurrency = useNativeCurrency(chainId) - const { tokensAsMap, isLoading } = useAllTokens(); const label = `${trade?.inputAmount?.currency.code}`; const labelInverted = `${trade?.outputAmount?.currency.code}`; - const sorobanContext = useSorobanReact(); - const getSwapValues = () => { if (!trade || !trade?.tradeType) return { formattedAmount0: '0', formattedAmount1: '0' }; @@ -100,39 +92,6 @@ export default function SwapModalFooter({ return { formattedAmount0, formattedAmount1 }; }; - const [pathArray, setPathArray] = useState([]); - - const [pathTokensIsLoading, setPathTokensIsLoading] = useState(false); - - 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); - - 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]); - return ( <> @@ -205,27 +164,7 @@ export default function SwapModalFooter({ - - - - - - - - - {pathArray?.map((contract, index) => ( - - {contract} - {index !== pathArray.length - 1 && } - - ))} - - - + {trade?.platform && ( diff --git a/src/components/Swap/SwapPathComponent.tsx b/src/components/Swap/SwapPathComponent.tsx new file mode 100644 index 00000000..d9e41bb9 --- /dev/null +++ b/src/components/Swap/SwapPathComponent.tsx @@ -0,0 +1,165 @@ +import React, { useEffect, useState } from 'react' +import { ChevronRight } from 'react-feather' +import { Box, styled } from 'soroswap-ui' +import Row, { RowBetween, RowFixed } from 'components/Row' +import { BodySmall, LabelSmall } from 'components/Text' +import { InterfaceTrade, PlatformType } from 'state/routing/types' +import { useSorobanReact } from '@soroban-react/core' +import { useAllTokens } from 'hooks/tokens/useAllTokens' +import { findToken } from 'hooks/tokens/useToken' +import { MouseoverTooltip } from 'components/Tooltip' +import { LoadingRows } from 'components/Loader/styled' + +export const PathBox = styled(Box)` + display: flex; + align-items: end; + justify-content: center; + flex-direction: row; +`; + +export const AggregatorPathBox = styled(Box)` + display: flex; + align-items: end; + justify-content: center; + flex-direction: column; +`; + +export function PathLoadingPlaceholder({ + syncing, + width, + children, +}: { + syncing: boolean; + width: number; + children: JSX.Element; +}) { + return syncing ? ( + +
+ + ) : ( + children + ); +} + +export interface distributionData { + parts: number; + path: string[]; + protocol: string; +} +export const formattedProtocolName = (protocol: string) => { + return protocol.charAt(0).toUpperCase() + protocol.slice(1); +} +export const calculatePercentage = (parts: number, totalParts: number) => { + return (parts / totalParts) * 100; +} + +function SwapPathComponent({ trade }: { trade: InterfaceTrade | undefined }) { + const sorobanContext = useSorobanReact(); + const { tokensAsMap, isLoading } = useAllTokens(); + + + const [pathArray, setPathArray] = useState([]); + const [distributionArray, setDistributionArray] = useState([]); + const [totalParts, setTotalParts] = useState(0); + const [pathTokensIsLoading, setPathTokensIsLoading] = useState(false); + + 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); + + 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); + } else if (trade.platform == PlatformType.AGGREGATOR) { + if (!trade?.distribution) return; + let tempDistributionArray: distributionData[] = []; + setPathTokensIsLoading(!pathTokensIsLoading); + for (let distribution of trade?.distribution) { + const promises = distribution.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 : '')); + tempDistributionArray.push({ path: fulfilledValues, parts: distribution.parts, protocol: distribution.protocol_id }); + setDistributionArray(tempDistributionArray); + setTotalParts(tempDistributionArray.reduce((acc, curr) => acc + curr.parts, 0)); + setPathTokensIsLoading(false); + } + } + })(); + }, [trade?.path, isLoading, sorobanContext]); + + return ( + + + + {trade?.platform == PlatformType.AGGREGATOR && distributionArray.length > 1 ? 'Paths:' : 'Path'} + + + + {(trade?.platform == PlatformType.ROUTER || trade?.platform == PlatformType.STELLAR_CLASSIC) && ( + + + {pathArray?.map((contract, index) => ( + + {contract} + {index !== pathArray.length - 1 && } + + ))} + + + )} + {(trade?.platform == PlatformType.AGGREGATOR) && ( + + + {distributionArray.map((distribution, index) => ( + + + {formattedProtocolName(distribution.protocol) + ':'} + {distribution.path.map((symbol, index) => ( + + {symbol} + {index !== distribution.path.length - 1 && ( + + )} + + ))} + ({calculatePercentage(distribution.parts, totalParts)}%) + + + ))} + + + )} + + ) +} + +export default SwapPathComponent diff --git a/src/hooks/useBestTrade.ts b/src/hooks/useBestTrade.ts index 210b57ee..d59d71f3 100644 --- a/src/hooks/useBestTrade.ts +++ b/src/hooks/useBestTrade.ts @@ -130,6 +130,7 @@ export function useBestTrade( tradeType, rawRoute: data, path: data?.trade.path, + distribution: data?.trade.distribution, priceImpact: data?.priceImpact, platform: data?.platform, };