@reservoir0x/relay-kit-ui
Version:
Relay is the Fastest and Cheapest Way to Bridge and Transact Across Chains.
162 lines • 12.2 kB
JavaScript
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
import { useState } from 'react';
import { Box, Button, Flex, Text } from '../primitives/index.js';
import { formatBN, formatDollar } from '../../utils/numbers.js';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faGasPump } from '@fortawesome/free-solid-svg-icons/faGasPump';
import { faChevronDown } from '@fortawesome/free-solid-svg-icons/faChevronDown';
import FetchingQuoteLoader from '../widgets/FetchingQuoteLoader.js';
import SwapRouteSelector from '../widgets/SwapRouteSelector.js';
import { CollapsibleContent, CollapsibleRoot, CollapsibleTrigger } from '../primitives/Collapsible.js';
import { faChevronRight, faClock, faInfoCircle } from '@fortawesome/free-solid-svg-icons';
import { PriceImpactTooltip } from './PriceImpactTooltip.js';
import { getSlippageRating, ratingToColor } from '../../utils/slippage.js';
import Tooltip from '../primitives/Tooltip.js';
import React from 'react';
const formatSwapRate = (rate) => {
return rate >= 1 ? formatBN(rate, 2, 18, false) : formatBN(rate, 4, 18, false);
};
const FeeBreakdown = ({ feeBreakdown, isFetchingQuote, quote, toToken, fromToken, toChain, supportsExternalLiquidity, useExternalLiquidity, setUseExternalLiquidity, timeEstimate, canonicalTimeEstimate, isSingleChainLocked, fromChainWalletVMSupported, isAutoSlippage, slippageInputBps, error }) => {
const [isOpen, setIsOpen] = useState(false);
const swapRate = quote?.details?.rate;
const originGasFee = feeBreakdown?.breakdown?.find((fee) => fee.id === 'origin-gas');
const originGasFeeFormatted = formatDollar(Math.abs(originGasFee?.usd.value ?? 0));
const [rateMode, setRateMode] = useState('input');
const isHighPriceImpact = Number(quote?.details?.totalImpact?.percent) < -3.5;
const isSameChain = toToken?.chainId === fromToken?.chainId;
const originSlippageTolerance = quote?.details?.slippageTolerance?.origin?.percent;
const destinationSlippageTolerance = quote?.details?.slippageTolerance?.destination?.percent;
const quoteSlippage = (isSameChain
? destinationSlippageTolerance === '0'
? originSlippageTolerance
: destinationSlippageTolerance
: destinationSlippageTolerance) ?? '0';
const slippageInputNumber = Number((Number(slippageInputBps ?? '0') / 100).toFixed(2));
const slippage = `${Math.max(Number(quoteSlippage), slippageInputNumber)}`;
const slippageRating = getSlippageRating(slippage);
const slippageRatingColor = ratingToColor[slippageRating];
const minimumAmountFormatted = quote?.details?.currencyOut?.minimumAmount
? formatBN(quote.details.currencyOut.minimumAmount, 6, toToken?.decimals, false)
: undefined;
const breakdown = [
{
title: 'Estimated time',
value: (_jsxs(Flex, { align: "center", css: {
gap: '1',
color: timeEstimate && timeEstimate.time <= 30
? '{colors.green.9}'
: '{colors.amber.9}'
}, children: [_jsx(FontAwesomeIcon, { icon: faClock, width: 16 }), _jsxs(Text, { style: "subtitle2", children: ["~ ", timeEstimate?.formattedTime] })] }))
},
{
title: 'Network cost',
value: (_jsxs(Flex, { align: "center", css: { gap: '1' }, children: [_jsx(FontAwesomeIcon, { icon: faGasPump, width: 16, style: { color: '#C1C8CD' } }), _jsx(Text, { style: "subtitle2", children: originGasFeeFormatted })] }))
},
{
title: 'Price Impact',
value: (_jsx(PriceImpactTooltip, { feeBreakdown: feeBreakdown, tooltipProps: { side: 'top', align: 'end' }, children: _jsx("div", { children: _jsxs(Flex, { align: "center", css: { gap: '1', color: 'gray8' }, children: [_jsx(Text, { style: "subtitle2", css: {
color: isHighPriceImpact ? 'red11' : undefined
}, children: feeBreakdown?.totalFees?.priceImpactPercentage }), _jsx(FontAwesomeIcon, { icon: faInfoCircle, width: 14, height: 14, style: {
display: 'inline-block',
marginLeft: 4
} })] }) }) }))
},
{
title: 'Max Slippage',
value: (_jsxs(Flex, { align: "center", css: { gap: '1' }, children: [isAutoSlippage ? (_jsx(Text, { style: "subtitle3", css: { py: '1px', px: '4px', bg: 'gray3', borderRadius: 100 }, children: "Auto" })) : null, _jsx(Tooltip, { side: "top", align: "end", content: minimumAmountFormatted ? (_jsxs(Flex, { direction: "row", css: { gap: '2' }, children: [_jsx(Text, { style: "subtitle2", color: "subtle", children: "Min. received" }), _jsxs(Text, { style: "subtitle2", children: [minimumAmountFormatted, " ", toToken?.symbol] })] })) : null, children: _jsxs(Text, { style: "subtitle2", css: { color: slippageRatingColor }, children: [slippage, "%"] }) })] }))
}
];
if (!isSingleChainLocked && fromChainWalletVMSupported) {
breakdown.unshift({
title: 'Route',
value: (_jsx(SwapRouteSelector, { chain: toChain, supportsExternalLiquidity: supportsExternalLiquidity, externalLiquidtySelected: useExternalLiquidity, onExternalLiquidityChange: (selected) => {
setUseExternalLiquidity(selected);
}, canonicalTimeEstimate: canonicalTimeEstimate?.formattedTime, error: error, trigger: _jsx(Button, { color: "ghost", size: "none", children: _jsxs(Flex, { css: { gap: '2', alignItems: 'center' }, children: [_jsx(Text, { style: "subtitle2", children: useExternalLiquidity ? 'Native' : 'Relay' }), supportsExternalLiquidity || useExternalLiquidity ? (_jsx(Box, { css: { color: 'gray11', width: 14, flexShrink: 0 }, children: _jsx(FontAwesomeIcon, { icon: faChevronRight, width: 14 }) })) : null] }) }) }))
});
}
if (!feeBreakdown) {
if (isFetchingQuote) {
return (_jsx(Box, { id: 'fee-breakdown-section', css: {
borderRadius: 'widget-card-border-radius',
backgroundColor: 'widget-background',
border: 'widget-card-border',
overflow: 'hidden',
mb: 'widget-card-section-gutter'
}, children: _jsx(FetchingQuoteLoader, { isLoading: isFetchingQuote, containerCss: {
mt: 0,
mb: 0,
px: '4',
py: '3',
width: '100%',
justifyContent: 'center'
} }) }));
}
else {
return null;
}
}
return (_jsxs(CollapsibleRoot, { open: isOpen, onOpenChange: setIsOpen, css: { mb: 'widget-card-section-gutter' }, children: [_jsx(CollapsibleTrigger, { css: {
borderRadius: 'widget-card-border-radius',
borderBottomRadius: isOpen ? '0' : 'widget-card-border-radius',
backgroundColor: 'widget-background',
border: 'widget-card-border',
borderBottom: isOpen ? 'none' : 'widget-card-border',
overflow: 'hidden',
transition: 'border-radius 300ms, border-bottom 0s',
transitionDelay: isOpen ? '0s, 0s' : '0s, 300ms'
}, id: 'fee-breakdown-section', children: _jsxs(Flex, { justify: "between", align: "center", css: {
flexDirection: 'row',
gap: '2',
width: '100%',
p: '3'
}, children: [_jsx("span", { style: {
cursor: 'pointer',
flexShrink: 1,
overflow: 'hidden',
height: 21
}, onClick: (e) => {
setRateMode(rateMode === 'input' ? 'output' : 'input');
e.preventDefault();
}, children: _jsx(ConversionRate, { fromToken: fromToken, toToken: toToken, rate: Number(swapRate), isInputMode: rateMode === 'input' }) }), _jsxs(Flex, { css: {
gap: '2',
color: timeEstimate && timeEstimate.time <= 30
? '{colors.green.9}'
: '{colors.amber.9}',
flexShrink: 0
}, align: "center", children: [!isOpen && timeEstimate && timeEstimate?.time !== 0 ? (_jsxs(_Fragment, { children: [_jsx(FontAwesomeIcon, { icon: faClock, width: 16 }), _jsxs(Text, { style: "subtitle2", css: { flexShrink: 0 }, children: ["~ ", timeEstimate?.formattedTime] }), _jsx(Flex, { justify: "center", align: "center", css: { color: 'gray6', height: 4 }, children: "\u2022" })] })) : null, !isOpen && (_jsxs(_Fragment, { children: [_jsx(FontAwesomeIcon, { icon: faGasPump, width: 16, style: { color: '#C1C8CD' } }), _jsx(Text, { style: "subtitle2", css: { flexShrink: 0 }, children: originGasFeeFormatted })] })), _jsx(Box, { css: {
marginLeft: '2',
transition: 'transform 300ms',
transform: isOpen ? 'rotate(-180deg)' : 'rotate(0)',
color: 'gray9'
}, children: _jsx(FontAwesomeIcon, { icon: faChevronDown, width: 12 }) })] })] }) }), _jsx(CollapsibleContent, { css: {
borderRadius: '0 0 12px 12px',
border: 'widget-card-border',
borderTop: 'none'
}, children: _jsx(Flex, { direction: "column", css: {
px: '3',
pb: '3',
pt: '0',
gap: '2',
backgroundColor: 'widget-background'
}, children: breakdown.map((item) => {
const showNativeBridgeWarning = item.title === 'Estimated time' &&
useExternalLiquidity &&
timeEstimate?.time &&
timeEstimate?.time > 86400;
return (_jsxs(React.Fragment, { children: [_jsxs(Flex, { justify: "between", align: "center", css: { width: '100%', gap: '4' }, children: [_jsx(Text, { style: "subtitle2", color: showNativeBridgeWarning ? 'warningSecondary' : 'subtle', css: { alignSelf: 'flex-start' }, children: item.title }), item.value] }), showNativeBridgeWarning ? (_jsx(Flex, { align: "center", css: {
gap: '2',
py: '2',
px: '3',
backgroundColor: 'amber2',
borderRadius: 12
}, children: _jsxs(Text, { style: "subtitle3", css: { color: 'amber12' }, children: ["Native bridge routes are expected to take", ' ', timeEstimate.formattedTime, " but could be longer due to unexpected delays"] }) })) : null] }, item.title));
}) }) })] }));
};
export default FeeBreakdown;
const ConversionRate = ({ fromToken, toToken, rate, isInputMode }) => {
const displayRate = isInputMode ? rate : 1 / rate;
const fromSymbol = isInputMode ? fromToken?.symbol : toToken?.symbol;
const toSymbol = isInputMode ? toToken?.symbol : fromToken?.symbol;
return (_jsx(Tooltip, { content: _jsxs(Text, { style: "subtitle2", children: ["1 ", fromSymbol, " = ", formatSwapRate(displayRate), " ", toSymbol] }), asChild: true, children: _jsx("div", { style: { width: '100%', minWidth: 0 }, children: _jsxs(Text, { style: "subtitle2", ellipsify: true, children: ["1 ", fromSymbol, " = ", formatSwapRate(displayRate), " ", toSymbol] }) }) }));
};
//# sourceMappingURL=FeeBreakdown.js.map