From 5cb295abe13686ba330d5a55cff105b8ef33294e Mon Sep 17 00:00:00 2001 From: pedromcunha Date: Wed, 16 Oct 2024 16:28:33 -0400 Subject: [PATCH 1/4] Move SwapRouteSelector outside of fees, specify error to show switch route ux, dynamic time for canonical estimate --- .../src/components/widgets/FeeBreakdown.tsx | 116 +++++++----------- .../components/widgets/SwapRouteSelector.tsx | 14 ++- .../components/widgets/SwapWidget/index.tsx | 27 ++-- .../components/widgets/SwapWidgetRenderer.tsx | 9 ++ 4 files changed, 82 insertions(+), 84 deletions(-) diff --git a/packages/ui/src/components/widgets/FeeBreakdown.tsx b/packages/ui/src/components/widgets/FeeBreakdown.tsx index 5c3d0a1a..7be4819a 100644 --- a/packages/ui/src/components/widgets/FeeBreakdown.tsx +++ b/packages/ui/src/components/widgets/FeeBreakdown.tsx @@ -6,7 +6,6 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faGasPump } from '@fortawesome/free-solid-svg-icons/faGasPump' import { faClock } from '@fortawesome/free-solid-svg-icons/faClock' import FetchingQuoteLoader from '../widgets/FetchingQuoteLoader.js' -import SwapRouteSelector from '../widgets/SwapRouteSelector.js' import type { RelayChain } from '@reservoir0x/relay-sdk' type Props = Pick< @@ -17,12 +16,7 @@ type Props = Pick< | 'toToken' | 'fromToken' | 'timeEstimate' - | 'supportsExternalLiquidity' - | 'useExternalLiquidity' - | 'setUseExternalLiquidity' -> & { - toChain?: RelayChain -} +> const formatSwapRate = (rate: number) => { return rate >= 1 ? formatNumber(rate, 2) : formatNumber(rate, 5) @@ -34,10 +28,6 @@ const FeeBreakdown: FC = ({ price, toToken, fromToken, - toChain, - supportsExternalLiquidity, - useExternalLiquidity, - setUseExternalLiquidity, timeEstimate }) => { const swapRate = price?.details?.rate @@ -76,79 +66,65 @@ const FeeBreakdown: FC = ({ } return ( - - { - setUseExternalLiquidity(selected) + + - - - - {timeEstimate && timeEstimate?.time !== 0 ? ( - <> - - ~ {timeEstimate?.formattedTime} - - - ) : null} - - {originGasFee?.usd} - + {timeEstimate && timeEstimate?.time !== 0 ? ( + <> + + ~ {timeEstimate?.formattedTime} + + + ) : null} + + {originGasFee?.usd} - + ) } diff --git a/packages/ui/src/components/widgets/SwapRouteSelector.tsx b/packages/ui/src/components/widgets/SwapRouteSelector.tsx index 8cf205fc..0c472c2a 100644 --- a/packages/ui/src/components/widgets/SwapRouteSelector.tsx +++ b/packages/ui/src/components/widgets/SwapRouteSelector.tsx @@ -10,16 +10,17 @@ type Props = { externalLiquidtySelected: boolean onExternalLiquidityChange: (externalLiquiditySelected: boolean) => void chain?: RelayChain + canonicalTimeEstimate?: string } const SwapRouteSelector: FC = ({ supportsExternalLiquidity, externalLiquidtySelected, onExternalLiquidityChange, - chain + chain, + canonicalTimeEstimate }) => { const [open, setOpen] = useState(false) - const chainName = chain?.displayName ?? '' return ( = ({ justifyContent: 'space-between', flexDirection: 'row', alignItems: 'center', - backgroundColor: 'transparent', gap: '3', px: '4', py: '3', borderRadius: 'widget-card-border-radius', border: 'widget-card-border', + backgroundColor: 'widget-background', + overflow: 'hidden', + mb: '6px', + '&:disabled': { cursor: 'default', backgroundColor: 'transparent', @@ -150,7 +154,9 @@ const SwapRouteSelector: FC = ({ Native - Standard time (>2m), unlimited transaction capacity + {canonicalTimeEstimate + ? `Standard time ${canonicalTimeEstimate}, unlimited transaction capacity` + : 'Unlimited transaction capacity'} diff --git a/packages/ui/src/components/widgets/SwapWidget/index.tsx b/packages/ui/src/components/widgets/SwapWidget/index.tsx index 640b3b14..d610e2ab 100644 --- a/packages/ui/src/components/widgets/SwapWidget/index.tsx +++ b/packages/ui/src/components/widgets/SwapWidget/index.tsx @@ -29,6 +29,7 @@ import type { AdaptedWallet } from '@reservoir0x/relay-sdk' import { evmDeadAddress, solDeadAddress } from '../../../constants/address.js' import { MultiWalletDropdown } from '../../common/MultiWalletDropdown.js' import { findSupportedWallet } from '../../../utils/solana.js' +import SwapRouteSelector from '../SwapRouteSelector.js' type BaseSwapWidgetProps = { defaultFromToken?: Token @@ -156,6 +157,7 @@ const SwapWidget: FC = ({ hasInsufficientBalance, isInsufficientLiquidityError, isCapacityExceededError, + isCouldNotExecuteError, maxCapacityFormatted, ctaCopy, isFromNative, @@ -165,6 +167,7 @@ const SwapWidget: FC = ({ isValidToAddress, supportsExternalLiquidity, useExternalLiquidity, + canonicalTimeEstimate, setUseExternalLiquidity, setDetails, setSwapError, @@ -223,6 +226,10 @@ const SwapWidget: FC = ({ isValidFromAddress ]) + const promptSwitchRoute = + (isCapacityExceededError || isCouldNotExecuteError) && + supportsExternalLiquidity + return ( = ({ + { + setUseExternalLiquidity(selected) + }} + /> = ({ fromToken={fromToken} price={price} timeEstimate={timeEstimate} - supportsExternalLiquidity={supportsExternalLiquidity} - useExternalLiquidity={useExternalLiquidity} - toChain={toChain} - setUseExternalLiquidity={(enabled) => { - setUseExternalLiquidity(enabled) - onAnalyticEvent?.(EventNames.SWAP_ROUTE_SELECTED, { - route: enabled ? 'canonical' : 'relay' - }) - }} /> = ({ mb: '6px' }} /> - {error && supportsExternalLiquidity ? ( + {promptSwitchRoute ? ( {isCapacityExceededError && maxCapacityFormatted != '0' ? ( diff --git a/packages/ui/src/components/widgets/SwapWidgetRenderer.tsx b/packages/ui/src/components/widgets/SwapWidgetRenderer.tsx index 92f5b0d6..4002f7b7 100644 --- a/packages/ui/src/components/widgets/SwapWidgetRenderer.tsx +++ b/packages/ui/src/components/widgets/SwapWidgetRenderer.tsx @@ -101,6 +101,7 @@ export type ChildrenProps = { hasInsufficientBalance: boolean isInsufficientLiquidityError?: boolean isCapacityExceededError?: boolean + isCouldNotExecuteError?: boolean maxCapacityWei?: string maxCapacityFormatted?: string ctaCopy: string @@ -108,6 +109,7 @@ export type ChildrenProps = { useExternalLiquidity: boolean supportsExternalLiquidity: boolean timeEstimate?: { time: number; formattedTime: string } + canonicalTimeEstimate?: { time: number; formattedTime: string } fetchingExternalLiquiditySupport: boolean isSvmSwap: boolean isValidFromAddress: boolean @@ -502,9 +504,14 @@ const SwapWidgetRenderer: FC = ({ const isCapacityExceededError = fetchQuoteDataErrorMessage?.includes( 'Amount is higher than the available liquidity' ) + const isCouldNotExecuteError = + fetchQuoteDataErrorMessage?.includes('Could not execute') const highRelayerServiceFee = isHighRelayerServiceFeeUsd(price) const relayerFeeProportion = calculateRelayerFeeProportionUsd(price) const timeEstimate = calculatePriceTimeEstimate(price?.details) + const canonicalTimeEstimate = calculatePriceTimeEstimate( + externalLiquiditySupport.data?.details + ) const isFromNative = fromToken?.address === fromChain?.currency?.address @@ -639,6 +646,7 @@ const SwapWidgetRenderer: FC = ({ hasInsufficientBalance, isInsufficientLiquidityError, isCapacityExceededError, + isCouldNotExecuteError, maxCapacityFormatted, maxCapacityWei, ctaCopy, @@ -646,6 +654,7 @@ const SwapWidgetRenderer: FC = ({ useExternalLiquidity, supportsExternalLiquidity, timeEstimate, + canonicalTimeEstimate, fetchingExternalLiquiditySupport: externalLiquiditySupport.isFetching, isSvmSwap, isValidFromAddress, From cea3f43bcc6b58fa312ea242b799b68fd5a0f101 Mon Sep 17 00:00:00 2001 From: pedromcunha Date: Wed, 16 Oct 2024 16:29:01 -0400 Subject: [PATCH 2/4] feat: changeset --- .changeset/dry-donkeys-cheat.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/dry-donkeys-cheat.md diff --git a/.changeset/dry-donkeys-cheat.md b/.changeset/dry-donkeys-cheat.md new file mode 100644 index 00000000..b5fe900d --- /dev/null +++ b/.changeset/dry-donkeys-cheat.md @@ -0,0 +1,5 @@ +--- +'@reservoir0x/relay-kit-ui': patch +--- + +Fix ux issues with canonical route selector From 8a97bdab22ef7d5025e0424f0b3bf4c9ae90985d Mon Sep 17 00:00:00 2001 From: pedromcunha Date: Wed, 16 Oct 2024 18:44:52 -0400 Subject: [PATCH 3/4] Design tweaks to show the route switcher seperately only if its an error --- .../src/components/widgets/FeeBreakdown.tsx | 121 +++++++++++------- .../components/widgets/SwapRouteSelector.tsx | 3 - .../components/widgets/SwapWidget/index.tsx | 42 ++++-- .../components/widgets/WidgetErrorWell.tsx | 8 +- 4 files changed, 114 insertions(+), 60 deletions(-) diff --git a/packages/ui/src/components/widgets/FeeBreakdown.tsx b/packages/ui/src/components/widgets/FeeBreakdown.tsx index 7be4819a..b2e330b7 100644 --- a/packages/ui/src/components/widgets/FeeBreakdown.tsx +++ b/packages/ui/src/components/widgets/FeeBreakdown.tsx @@ -6,6 +6,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faGasPump } from '@fortawesome/free-solid-svg-icons/faGasPump' import { faClock } from '@fortawesome/free-solid-svg-icons/faClock' import FetchingQuoteLoader from '../widgets/FetchingQuoteLoader.js' +import SwapRouteSelector from '../widgets/SwapRouteSelector.js' import type { RelayChain } from '@reservoir0x/relay-sdk' type Props = Pick< @@ -16,7 +17,13 @@ type Props = Pick< | 'toToken' | 'fromToken' | 'timeEstimate' -> + | 'supportsExternalLiquidity' + | 'useExternalLiquidity' + | 'setUseExternalLiquidity' + | 'canonicalTimeEstimate' +> & { + toChain?: RelayChain +} const formatSwapRate = (rate: number) => { return rate >= 1 ? formatNumber(rate, 2) : formatNumber(rate, 5) @@ -28,7 +35,12 @@ const FeeBreakdown: FC = ({ price, toToken, fromToken, - timeEstimate + toChain, + supportsExternalLiquidity, + useExternalLiquidity, + setUseExternalLiquidity, + timeEstimate, + canonicalTimeEstimate }) => { const swapRate = price?.details?.rate const originGasFee = feeBreakdown?.breakdown?.find( @@ -66,65 +78,80 @@ const FeeBreakdown: FC = ({ } return ( - - - + canonicalTimeEstimate={canonicalTimeEstimate?.formattedTime} + /> + - {timeEstimate && timeEstimate?.time !== 0 ? ( - <> - - ~ {timeEstimate?.formattedTime} - - - ) : null} - - {originGasFee?.usd} + + + + {timeEstimate && timeEstimate?.time !== 0 ? ( + <> + + ~ {timeEstimate?.formattedTime} + + + ) : null} + + {originGasFee?.usd} + - + ) } diff --git a/packages/ui/src/components/widgets/SwapRouteSelector.tsx b/packages/ui/src/components/widgets/SwapRouteSelector.tsx index 0c472c2a..15067a19 100644 --- a/packages/ui/src/components/widgets/SwapRouteSelector.tsx +++ b/packages/ui/src/components/widgets/SwapRouteSelector.tsx @@ -55,9 +55,6 @@ const SwapRouteSelector: FC = ({ py: '3', borderRadius: 'widget-card-border-radius', border: 'widget-card-border', - backgroundColor: 'widget-background', - overflow: 'hidden', - mb: '6px', '&:disabled': { cursor: 'default', diff --git a/packages/ui/src/components/widgets/SwapWidget/index.tsx b/packages/ui/src/components/widgets/SwapWidget/index.tsx index d610e2ab..77a783bc 100644 --- a/packages/ui/src/components/widgets/SwapWidget/index.tsx +++ b/packages/ui/src/components/widgets/SwapWidget/index.tsx @@ -805,15 +805,28 @@ const SwapWidget: FC = ({ - { - setUseExternalLiquidity(selected) - }} - /> + {error && !isFetchingPrice ? ( + + { + setUseExternalLiquidity(selected) + }} + /> + + ) : null} = ({ fromToken={fromToken} price={price} timeEstimate={timeEstimate} + supportsExternalLiquidity={supportsExternalLiquidity} + useExternalLiquidity={useExternalLiquidity} + toChain={toChain} + setUseExternalLiquidity={(enabled) => { + setUseExternalLiquidity(enabled) + onAnalyticEvent?.(EventNames.SWAP_ROUTE_SELECTED, { + route: enabled ? 'canonical' : 'relay' + }) + }} + canonicalTimeEstimate={canonicalTimeEstimate} /> = ({ currency={toToken} isHighRelayerServiceFee={highRelayerServiceFee} isCapacityExceededError={isCapacityExceededError} + isCouldNotExecuteError={isCouldNotExecuteError} maxCapacity={maxCapacityFormatted} relayerFeeProportion={relayerFeeProportion} supportsExternalLiquidity={supportsExternalLiquidity} diff --git a/packages/ui/src/components/widgets/WidgetErrorWell.tsx b/packages/ui/src/components/widgets/WidgetErrorWell.tsx index a50bf340..c5955175 100644 --- a/packages/ui/src/components/widgets/WidgetErrorWell.tsx +++ b/packages/ui/src/components/widgets/WidgetErrorWell.tsx @@ -16,6 +16,7 @@ type Props = { relayerFeeProportion?: bigint | 0 isHighRelayerServiceFee?: boolean isCapacityExceededError?: boolean + isCouldNotExecuteError?: boolean maxCapacity?: string supportsExternalLiquidity?: boolean containerCss?: Styles @@ -29,6 +30,7 @@ export const WidgetErrorWell: FC = ({ relayerFeeProportion, isHighRelayerServiceFee, isCapacityExceededError, + isCouldNotExecuteError, maxCapacity, supportsExternalLiquidity, containerCss @@ -75,7 +77,11 @@ export const WidgetErrorWell: FC = ({ ) - } else if (supportsExternalLiquidity && currency) { + } else if ( + supportsExternalLiquidity && + isCouldNotExecuteError && + currency + ) { return ( Date: Wed, 16 Oct 2024 23:07:49 -0400 Subject: [PATCH 4/4] Design tweaks for SwapRouteSelector --- packages/ui/src/components/widgets/SwapRouteSelector.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/components/widgets/SwapRouteSelector.tsx b/packages/ui/src/components/widgets/SwapRouteSelector.tsx index 15067a19..56af5bc2 100644 --- a/packages/ui/src/components/widgets/SwapRouteSelector.tsx +++ b/packages/ui/src/components/widgets/SwapRouteSelector.tsx @@ -30,7 +30,7 @@ const SwapRouteSelector: FC = ({ } }} contentProps={{ - sideOffset: 12, + sideOffset: 8, align: 'end', css: { maxWidth: 248, @@ -152,7 +152,7 @@ const SwapRouteSelector: FC = ({ Native {canonicalTimeEstimate - ? `Standard time ${canonicalTimeEstimate}, unlimited transaction capacity` + ? `Standard time ~${canonicalTimeEstimate}, unlimited transaction capacity` : 'Unlimited transaction capacity'}