From 2eeea09cf96358a4a07d8253e4549650bab51506 Mon Sep 17 00:00:00 2001 From: Matias Poblete Date: Wed, 21 Aug 2024 11:18:17 -0400 Subject: [PATCH 01/10] =?UTF-8?q?=F0=9F=A9=B9Fix=20unhandled=20error=20tha?= =?UTF-8?q?t=20collapses=20the=20app=20when=20customSlippage=20is=20set?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Settings/MaxSlippageSettings/index.tsx | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/components/Settings/MaxSlippageSettings/index.tsx b/src/components/Settings/MaxSlippageSettings/index.tsx index 603199cd..286c9c0d 100644 --- a/src/components/Settings/MaxSlippageSettings/index.tsx +++ b/src/components/Settings/MaxSlippageSettings/index.tsx @@ -103,15 +103,16 @@ export default function MaxSlippageSettings({ autoSlippage }: { autoSlippage: nu function setCustomSlippage() { // When switching to custom slippage, use `auto` value as a default. - setUserSlippageTolerance(autoSlippage); selectSlippageInput(); } - const removeTrailingZeroes = (num: number) => { - return num + const removeTrailingZeroes = (num: number | SlippageTolerance.Auto) => { + if (num === SlippageTolerance.Auto) return; + const parsedNum = num .toFixed(2) .toString() .replace(/0{1,}$/, ''); + return parsedNum[parsedNum.length - 1] === '.' ? parsedNum.slice(0, -1) : parsedNum; }; useEffect(function () { @@ -139,8 +140,8 @@ export default function MaxSlippageSettings({ autoSlippage }: { autoSlippage: nu } button={ - - {isAuto ? <>Auto : `${removeTrailingZeroes(userSlippageTolerance)}%`} + + {isAuto ? 'Auto' : `${removeTrailingZeroes(userSlippageTolerance)}%`} } > @@ -173,11 +174,11 @@ export default function MaxSlippageSettings({ autoSlippage }: { autoSlippage: nu onChange={(e) => parseSlippageInput(e.target.value)} onBlur={() => { // When the input field is blurred, reset the input field to the default value - setSlippageInput(DEFAULT_SLIPPAGE_INPUT_VALUE); - setSlippageError(false); + //setSlippageInput(DEFAULT_SLIPPAGE_INPUT_VALUE); + //setSlippageError(false); }} onFocus={setCustomSlippage} - type="number" + type="text" step="0.1" /> % From 0bc5f2579bd00095510fb4adc27a4833887940cb Mon Sep 17 00:00:00 2001 From: Matias Poblete Date: Wed, 21 Aug 2024 16:44:42 -0400 Subject: [PATCH 02/10] =?UTF-8?q?=E2=9C=85Add=20tests=20to=20slippage=20co?= =?UTF-8?q?nfigs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cypress/e2e/flows.test.ts | 67 +++++++++++++++---- .../Settings/MaxSlippageSettings/index.tsx | 8 +-- 2 files changed, 58 insertions(+), 17 deletions(-) diff --git a/cypress/e2e/flows.test.ts b/cypress/e2e/flows.test.ts index c8826067..3e4625b7 100644 --- a/cypress/e2e/flows.test.ts +++ b/cypress/e2e/flows.test.ts @@ -133,19 +133,12 @@ describe('Select tokens & input amount', () => { describe('Input & output amount validation', () => { it('should type an input amount & wait for output amount', () => { cy.visit('/swap'); - //Select input asset -/* cy.get('[data-testid="swap__input__panel"]').within(() => { - cy.get('[data-testid="swap__token__select"]').click(); - }); - cy.get('[data-testid="currency__list__XLM"]').click(); - */ //Select output asset cy.get('[data-testid="swap__output__panel"]').within(() => { cy.get('[data-testid="swap__token__select"]').click(); }); - cy.get('[data-testid="token-search-input"]').type('ngnt'); - cy.get('[data-testid="currency__list__NGNT"]').click(); - + cy.get('[data-testid="token-search-input"]').type('usdc'); + cy.get('[data-testid="currency__list__USDC"]').click(); //Input amount cy.get('[data-testid="swap__input__panel"]').within(() => { @@ -168,8 +161,8 @@ describe('Input & output amount validation', () => { cy.get('[data-testid="swap__input__panel"]').within(() => { cy.get('[data-testid="swap__token__select"]').click(); }); - cy.get('[data-testid="token-search-input"]').type('ngnt'); - cy.get('[data-testid="currency__list__NGNT"]').click(); + cy.get('[data-testid="token-search-input"]').type('usdc'); + cy.get('[data-testid="currency__list__USDC"]').click(); //Input amount cy.get('[data-testid="swap__output__panel"]').within(() => { cy.get('.token-amount-input').type('{backspace}'); @@ -179,14 +172,62 @@ describe('Input & output amount validation', () => { cy.wait(2500); cy.screenshot() cy.get('[data-testid="swap-input-input-panel"]').invoke('val').then((inputAmount: any)=>{ - const belowOutput = Math.floor(parseFloat(outputAmount) * 0.9); - const aboveOutput = Math.ceil(parseFloat(outputAmount) * 1.1); + const belowOutput = Math.floor(parseFloat(outputAmount) * 0.5); + const aboveOutput = Math.ceil(parseFloat(outputAmount) * 1.5); expect(parseFloat(inputAmount)).within(belowOutput, aboveOutput) }) }) }) }); + +describe('Slippage tolerance config', ()=>{ + it('should change slippage tolerance', ()=>{ + cy.visit('/swap'); + cy.get('[data-testid="open-settings-dialog-button"]').click(); + cy.get('[data-testid="max-slippage-settings"]').click(); + cy.get('[data-testid="slippage-input"]').type('1.05'); + cy.get('[data-testid="max-slippage-settings"]').contains('1.05%'); + }) + it('Show display an alert if slippage is too high', ()=>{ + cy.visit('/swap'); + cy.get('[data-testid="open-settings-dialog-button"]').click(); + cy.get('[data-testid="max-slippage-settings"]').click(); + cy.get('[data-testid="slippage-input"]').type('10'); + cy.get('[data-testid="max-slippage-settings"]').contains('10%'); + cy.get('[data-testid="slippage-alert"]').should('exist'); + cy.get('[data-testid="slippage-alert-too-high"]').should('exist'); + }) + it('Show display an alert if slippage is too low', ()=>{ + cy.visit('/swap'); + cy.get('[data-testid="open-settings-dialog-button"]').click(); + cy.get('[data-testid="max-slippage-settings"]').click(); + cy.get('[data-testid="slippage-input"]').type('0.01'); + cy.get('[data-testid="max-slippage-settings"]').contains('0.01%'); + cy.get('[data-testid="slippage-alert"]').should('exist'); + cy.get('[data-testid="slippage-alert-too-low"]').should('exist'); + }) + it('should keep the slippage tolerance after closing the settings', ()=>{ + cy.visit('/swap'); + cy.get('[data-testid="open-settings-dialog-button"]').click(); + cy.get('[data-testid="max-slippage-settings"]').click(); + cy.get('[data-testid="slippage-input"]').type('1.05'); + cy.get('[data-testid="open-settings-dialog-button"]').click(); + cy.get('[data-testid="max-slippage-settings"]').should('not.exist'); + cy.get('[data-testid="open-settings-dialog-button"]').click(); + cy.get('[data-testid="max-slippage-settings"]').should('exist') + cy.get('[data-testid="max-slippage-settings"]').contains('1.05%'); + }) + it('Should set the slippage tolerance to auto when pressing "auto" button', ()=>{ + cy.visit('/swap'); + cy.get('[data-testid="open-settings-dialog-button"]').click(); + cy.get('[data-testid="max-slippage-settings"]').click(); + cy.get('[data-testid="slippage-input"]').type('1.05'); + cy.get('[data-testid="max-slippage-settings"]').contains('1.05%'); + cy.get('[data-testid="slippage-auto-button"]').click(); + cy.get('[data-testid="max-slippage-settings"]').contains('Auto'); + }) +}) // Navigation flow describe('Navigation flow', () => { it('should render the navbar', () => { diff --git a/src/components/Settings/MaxSlippageSettings/index.tsx b/src/components/Settings/MaxSlippageSettings/index.tsx index 286c9c0d..77facf7f 100644 --- a/src/components/Settings/MaxSlippageSettings/index.tsx +++ b/src/components/Settings/MaxSlippageSettings/index.tsx @@ -155,7 +155,7 @@ export default function MaxSlippageSettings({ autoSlippage }: { autoSlippage: nu }} isActive={isAuto} > - + Auto @@ -186,14 +186,14 @@ export default function MaxSlippageSettings({ autoSlippage }: { autoSlippage: nu {tooLow || tooHigh ? ( - + {tooLow ? ( -
+
Slippage below {MINIMUM_RECOMMENDED_SLIPPAGE.toFixed(2)}% may result in a failed transaction
) : ( -
Your transaction may be frontrun and result in an unfavorable trade.
+
Your transaction may be frontrun and result in an unfavorable trade.
)} From efc4ca7a13572b641ae19a0fdcf868da1982a302 Mon Sep 17 00:00:00 2001 From: Matias Poblete Date: Wed, 21 Aug 2024 16:51:02 -0400 Subject: [PATCH 03/10] =?UTF-8?q?=F0=9F=94=A5Removed=20unused=20screenshot?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cypress/e2e/flows.test.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/cypress/e2e/flows.test.ts b/cypress/e2e/flows.test.ts index 3e4625b7..d6bc8184 100644 --- a/cypress/e2e/flows.test.ts +++ b/cypress/e2e/flows.test.ts @@ -146,7 +146,6 @@ describe('Input & output amount validation', () => { }); //await for calcs cy.wait(5000); - cy.screenshot() //Get the output amount cy.get('[data-testid="swap-output-input-panel"]').invoke('val').as('outputAmount'); //Get the input amount @@ -170,7 +169,6 @@ describe('Input & output amount validation', () => { cy.get('.token-amount-input').type('1'); }); cy.wait(2500); - cy.screenshot() cy.get('[data-testid="swap-input-input-panel"]').invoke('val').then((inputAmount: any)=>{ const belowOutput = Math.floor(parseFloat(outputAmount) * 0.5); const aboveOutput = Math.ceil(parseFloat(outputAmount) * 1.5); From 5e8c8a836a7b9acd66784ae05872fcf0453bd577 Mon Sep 17 00:00:00 2001 From: Matias Poblete <86752543+MattPoblete@users.noreply.github.com> Date: Wed, 28 Aug 2024 09:07:34 -0400 Subject: [PATCH 04/10] =?UTF-8?q?=E2=9C=A8Show=20aggregator=20paths=20in?= =?UTF-8?q?=20advanced=20swap=20details?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Swap/AdvancedSwapDetails.tsx | 59 ++++++++++++++++++--- src/hooks/useBestTrade.ts | 1 + 2 files changed, 52 insertions(+), 8 deletions(-) diff --git a/src/components/Swap/AdvancedSwapDetails.tsx b/src/components/Swap/AdvancedSwapDetails.tsx index 6268655f..422e9471 100644 --- a/src/components/Swap/AdvancedSwapDetails.tsx +++ b/src/components/Swap/AdvancedSwapDetails.tsx @@ -1,11 +1,11 @@ -import { BodySmall } from 'components/Text'; +import { BodySmall, LabelSmall } 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'; -import { RowBetween, RowFixed } from 'components/Row'; +import Row, { RowBetween, RowFixed } from 'components/Row'; import { Separator } from 'components/SearchModal/styleds'; import { MouseoverTooltip } from 'components/Tooltip'; import { formatTokenAmount } from 'helpers/format'; @@ -17,9 +17,9 @@ import { InterfaceTrade, PlatformType } from 'state/routing/types'; export const PathBox = styled(Box)` display: flex; - align-items: center; + align-items: end; justify-content: center; - flex-direction: row; + flex-direction: column; `; interface AdvancedSwapDetailsProps { @@ -28,6 +28,10 @@ interface AdvancedSwapDetailsProps { syncing?: boolean; networkFees: number | null; } +interface distributionData { + path: string[]; + parts: number; +} export function TextWithLoadingPlaceholder({ syncing, @@ -76,9 +80,14 @@ export function AdvancedSwapDetails({ const { tokensAsMap, isLoading } = useAllTokens(); const [pathArray, setPathArray] = useState([]); - + const [distributionArray, setDistributionArray] = useState([]); + const [totalParts, setTotalParts] = useState(0); const [pathTokensIsLoading, setPathTokensIsLoading] = useState(false); + const calculatePercentage = (parts: number) => { + return (parts / totalParts) * 100; + } + useEffect(() => { (async () => { if (!trade?.path || isLoading) return; @@ -104,6 +113,25 @@ export function AdvancedSwapDetails({ }); setPathArray(codes); setPathTokensIsLoading(false); + } else if (trade.platform == PlatformType.AGGREGATOR) { + if (!trade?.distribution) return; + let tempDistributionArray: distributionData[] = []; + setPathTokensIsLoading(true); + 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 }); + setDistributionArray(tempDistributionArray); + setTotalParts(tempDistributionArray.reduce((acc, curr) => acc + curr.parts, 0)); + setPathTokensIsLoading(false); + } } })(); }, [trade?.path, isLoading, sorobanContext]); @@ -161,24 +189,39 @@ export function AdvancedSwapDetails({ { - + - Path + {trade?.platform == PlatformType.AGGREGATOR && distributionArray.length > 1 ? 'Paths:' : 'Path'} - {pathArray?.map((contract, index) => ( + {(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) => ( + + + {distribution.path.map((symbol, index) => ( + + {symbol} + {index !== distribution.path.length - 1 && ( + + )} + + ))} + ({calculatePercentage(distribution.parts)}%) + + + ))} 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, }; From 47a38b94c8955a635a7d5af07bbe57e9a4ab5d99 Mon Sep 17 00:00:00 2001 From: Matias Poblete <86752543+MattPoblete@users.noreply.github.com> Date: Thu, 29 Aug 2024 08:00:39 -0400 Subject: [PATCH 05/10] =?UTF-8?q?=E2=9C=A8Created=20SwapPath=20component?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Swap/AdvancedSwapDetails.tsx | 129 +++---------------- src/components/Swap/SwapModalFooter.tsx | 77 ++---------- src/components/Swap/SwapPathComponent.tsx | 132 ++++++++++++++++++++ 3 files changed, 154 insertions(+), 184 deletions(-) create mode 100644 src/components/Swap/SwapPathComponent.tsx diff --git a/src/components/Swap/AdvancedSwapDetails.tsx b/src/components/Swap/AdvancedSwapDetails.tsx index 422e9471..546388d1 100644 --- a/src/components/Swap/AdvancedSwapDetails.tsx +++ b/src/components/Swap/AdvancedSwapDetails.tsx @@ -1,37 +1,24 @@ -import { BodySmall, LabelSmall } 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'; -import Row, { RowBetween, RowFixed } from 'components/Row'; +import { RowBetween, RowFixed } from 'components/Row'; 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: end; - justify-content: center; - flex-direction: column; -`; - -interface AdvancedSwapDetailsProps { +export interface AdvancedSwapDetailsProps { trade: InterfaceTrade | undefined; allowedSlippage: number; syncing?: boolean; networkFees: number | null; } -interface distributionData { - path: string[]; - parts: number; -} export function TextWithLoadingPlaceholder({ syncing, @@ -67,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, @@ -76,66 +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 [distributionArray, setDistributionArray] = useState([]); - const [totalParts, setTotalParts] = useState(0); - const [pathTokensIsLoading, setPathTokensIsLoading] = useState(false); - - const calculatePercentage = (parts: number) => { - return (parts / totalParts) * 100; - } - - 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(true); - 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 }); - setDistributionArray(tempDistributionArray); - setTotalParts(tempDistributionArray.reduce((acc, curr) => acc + curr.parts, 0)); - setPathTokensIsLoading(false); - } - } - })(); - }, [trade?.path, isLoading, sorobanContext]); - return ( @@ -188,44 +124,7 @@ export function AdvancedSwapDetails({ - { - - - - {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) => ( - - - {distribution.path.map((symbol, index) => ( - - {symbol} - {index !== distribution.path.length - 1 && ( - - )} - - ))} - ({calculatePercentage(distribution.parts)}%) - - - ))} - - - - } + {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..69564cd1 --- /dev/null +++ b/src/components/Swap/SwapPathComponent.tsx @@ -0,0 +1,132 @@ +import React, { useEffect, useState } from 'react' +import { TextWithLoadingPlaceholder } from './AdvancedSwapDetails' +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' + +export const PathBox = styled(Box)` + display: flex; + align-items: end; + justify-content: center; + flex-direction: column; +`; + +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(true); + 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 From 42e8af48d5c4833059d1df7cf02d38e34f6bda50 Mon Sep 17 00:00:00 2001 From: Matias Poblete <86752543+MattPoblete@users.noreply.github.com> Date: Fri, 30 Aug 2024 17:37:36 -0400 Subject: [PATCH 06/10] =?UTF-8?q?=F0=9F=A9=B9Fix=20mainnet=20paths=20displ?= =?UTF-8?q?ay?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Swap/SwapPathComponent.tsx | 91 +++++++++++++++-------- 1 file changed, 62 insertions(+), 29 deletions(-) diff --git a/src/components/Swap/SwapPathComponent.tsx b/src/components/Swap/SwapPathComponent.tsx index 69564cd1..695d9a6c 100644 --- a/src/components/Swap/SwapPathComponent.tsx +++ b/src/components/Swap/SwapPathComponent.tsx @@ -1,5 +1,4 @@ import React, { useEffect, useState } from 'react' -import { TextWithLoadingPlaceholder } from './AdvancedSwapDetails' import { ChevronRight } from 'react-feather' import { Box, styled } from 'soroswap-ui' import Row, { RowBetween, RowFixed } from 'components/Row' @@ -9,14 +8,40 @@ 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[]; @@ -37,7 +62,7 @@ function SwapPathComponent({ trade }: { trade: InterfaceTrade | undefined }) { const [pathArray, setPathArray] = useState([]); const [distributionArray, setDistributionArray] = useState([]); const [totalParts, setTotalParts] = useState(0); - const [pathTokensIsLoading, setPathTokensIsLoading] = useState(false); + const [pathTokensIsLoading, setPathTokensIsLoading] = useState(false); useEffect(() => { (async () => { @@ -67,7 +92,7 @@ function SwapPathComponent({ trade }: { trade: InterfaceTrade | undefined }) { } else if (trade.platform == PlatformType.AGGREGATOR) { if (!trade?.distribution) return; let tempDistributionArray: distributionData[] = []; - setPathTokensIsLoading(true); + setPathTokensIsLoading(!pathTokensIsLoading); for (let distribution of trade?.distribution) { const promises = distribution.path.map(async (contract) => { const asset = await findToken(contract, tokensAsMap, sorobanContext); @@ -99,32 +124,40 @@ function SwapPathComponent({ trade }: { trade: InterfaceTrade | undefined }) { - - - {(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)}%) - - - ))} - - + {(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)}%) + + + ))} + + + )} ) } From ec76a8e1355c80404791c70a486be0d5867ce4aa Mon Sep 17 00:00:00 2001 From: Matias Poblete <86752543+MattPoblete@users.noreply.github.com> Date: Mon, 2 Sep 2024 08:36:13 -0400 Subject: [PATCH 07/10] =?UTF-8?q?=E2=9C=A8Add=20data=20testid=20to=20new?= =?UTF-8?q?=20component?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Swap/AdvancedSwapDetails.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Swap/AdvancedSwapDetails.tsx b/src/components/Swap/AdvancedSwapDetails.tsx index 546388d1..a62e3084 100644 --- a/src/components/Swap/AdvancedSwapDetails.tsx +++ b/src/components/Swap/AdvancedSwapDetails.tsx @@ -124,7 +124,7 @@ export function AdvancedSwapDetails({ - + {trade?.platform && ( From 28d526a7e7a835ec09528b329187b4d872a68315 Mon Sep 17 00:00:00 2001 From: Matias Poblete Date: Mon, 2 Sep 2024 09:09:05 -0400 Subject: [PATCH 08/10] =?UTF-8?q?=F0=9F=A9=B9Fix=20testid=20tags?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Swap/AdvancedSwapDetails.tsx | 2 +- src/components/Swap/SwapPathComponent.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/Swap/AdvancedSwapDetails.tsx b/src/components/Swap/AdvancedSwapDetails.tsx index a62e3084..546388d1 100644 --- a/src/components/Swap/AdvancedSwapDetails.tsx +++ b/src/components/Swap/AdvancedSwapDetails.tsx @@ -124,7 +124,7 @@ export function AdvancedSwapDetails({ - + {trade?.platform && ( diff --git a/src/components/Swap/SwapPathComponent.tsx b/src/components/Swap/SwapPathComponent.tsx index 695d9a6c..d9e41bb9 100644 --- a/src/components/Swap/SwapPathComponent.tsx +++ b/src/components/Swap/SwapPathComponent.tsx @@ -126,7 +126,7 @@ function SwapPathComponent({ trade }: { trade: InterfaceTrade | undefined }) { {(trade?.platform == PlatformType.ROUTER || trade?.platform == PlatformType.STELLAR_CLASSIC) && ( - + {pathArray?.map((contract, index) => ( {contract} @@ -138,7 +138,7 @@ function SwapPathComponent({ trade }: { trade: InterfaceTrade | undefined }) { )} {(trade?.platform == PlatformType.AGGREGATOR) && ( - + {distributionArray.map((distribution, index) => ( From d3e032068d248c53e67a8ac5b0c101bf8ecc65a1 Mon Sep 17 00:00:00 2001 From: Matias Poblete <86752543+MattPoblete@users.noreply.github.com> Date: Mon, 2 Sep 2024 13:21:09 -0400 Subject: [PATCH 09/10] =?UTF-8?q?=E2=AC=86=EF=B8=8FUpdated=20soroban=20rea?= =?UTF-8?q?ct=20contracts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 76a3ad00..9c5c6835 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "@scio-labs/use-inkathon": "^0.8.1", "@soroban-react/chains": "9.1.13", "@soroban-react/connect-button": "9.1.13", - "@soroban-react/contracts": "9.1.13", + "@soroban-react/contracts": "9.2.2", "@soroban-react/core": "9.1.13", "@soroban-react/events": "9.1.13", "@soroban-react/freighter": "9.1.13", From ce68e517d432bb0fad6ff2d6c6f3ede22fc26235 Mon Sep 17 00:00:00 2001 From: Matias Poblete <86752543+MattPoblete@users.noreply.github.com> Date: Mon, 2 Sep 2024 16:03:45 -0400 Subject: [PATCH 10/10] =?UTF-8?q?=E2=AC=86=EF=B8=8FUpdate=20yarn=20lock?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yarn.lock | 39 +++++++-------------------------------- 1 file changed, 7 insertions(+), 32 deletions(-) diff --git a/yarn.lock b/yarn.lock index 1a7cf91f..6decf942 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2004,10 +2004,10 @@ dependencies: "@soroban-react/core" "^9.1.13" -"@soroban-react/contracts@9.1.13": - version "9.1.13" - resolved "https://registry.yarnpkg.com/@soroban-react/contracts/-/contracts-9.1.13.tgz#e50e8e1a03305fbba1c4e74345f521db7c48a53f" - integrity sha512-DqV6Ky71o54+nofiPv076RktfX2/KP/LW/d7YjfXegR0bt4yCyTTlwsry7CoH/PRLq4TbCoUWTEnfgJhJAFYPw== +"@soroban-react/contracts@9.2.2": + version "9.2.2" + resolved "https://registry.yarnpkg.com/@soroban-react/contracts/-/contracts-9.2.2.tgz#ff43dae1335227e7294aa9c32923e7a76a52f442" + integrity sha512-P8juk3/J9Nyw43Jb+4WuKmPtw3jbElD43H0YYpZe+mzTKfy5BT/WNA36D4xAVV7XQaqAmDj8ZIWd6wNlS6F0Xg== dependencies: "@soroban-react/core" "^9.1.13" "@stellar/stellar-sdk" "12.2.0" @@ -7957,16 +7957,7 @@ strict-event-emitter@^0.5.1: resolved "https://registry.yarnpkg.com/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz#1602ece81c51574ca39c6815e09f1a3e8550bd93" integrity sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ== -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -8046,14 +8037,7 @@ string.prototype.trimstart@^1.0.8: define-properties "^1.2.1" es-object-atoms "^1.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -8746,7 +8730,7 @@ word-wrap@^1.2.5: resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -8764,15 +8748,6 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"