@reservoir0x/relay-kit-ui
Version:
Relay is the Fastest and Cheapest Way to Bridge and Transact Across Chains.
671 lines (670 loc) • 77.5 kB
JavaScript
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
import { Flex, Button, Text, Box } from '../../primitives/index.js';
import { useCallback, useContext, useEffect, useMemo, useState } from 'react';
import { useRelayClient } from '../../../hooks/index.js';
import { formatUnits } from 'viem';
import { usePublicClient } from 'wagmi';
import { formatFixedLength, formatDollar, formatNumber } from '../../../utils/numbers.js';
import AmountInput from '../../common/AmountInput.js';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { SwitchIcon } from '../../../icons/index.js';
import { getFeeBufferAmount } from '../../../utils/nativeMaxAmount.js';
import { WidgetErrorWell } from '../WidgetErrorWell.js';
import { BalanceDisplay } from '../../common/BalanceDisplay.js';
import { EventNames } from '../../../constants/events.js';
import SwapWidgetRenderer from '../SwapWidgetRenderer.js';
import WidgetContainer from '../WidgetContainer.js';
import SwapButton from '../SwapButton.js';
import TokenSelectorContainer from '../TokenSelectorContainer.js';
import FeeBreakdown from '../FeeBreakdown.js';
import { faArrowDown, faClipboard } from '@fortawesome/free-solid-svg-icons';
import { TokenTrigger } from '../../common/TokenSelector/triggers/TokenTrigger.js';
import { MultiWalletDropdown } from '../../common/MultiWalletDropdown.js';
import { findSupportedWallet } from '../../../utils/address.js';
import { isDeadAddress, tronDeadAddress } from '@reservoir0x/relay-sdk';
import SwapRouteSelector from '../SwapRouteSelector.js';
import { ProviderOptionsContext } from '../../../providers/RelayKitProvider.js';
import { findBridgableToken } from '../../../utils/tokens.js';
import { isChainLocked } from '../../../utils/tokenSelector.js';
import TokenSelector from '../../common/TokenSelector/TokenSelector.js';
import { UnverifiedTokenModal } from '../../common/UnverifiedTokenModal.js';
import { alreadyAcceptedToken } from '../../../utils/localStorage.js';
import GasTopUpSection from './GasTopUpSection.js';
import { calculateUsdValue, getSwapEventData } from '../../../utils/quote.js';
const SwapWidget = ({ fromToken, setFromToken, toToken, setToToken, defaultToAddress, defaultAmount, defaultTradeType, slippageTolerance, lockToToken = false, lockFromToken = false, lockChainId, singleChainMode = false, wallet, multiWalletSupportEnabled = false, linkedWallets, supportedWalletVMs, disableInputAutoFocus = false, popularChainIds, disablePasteWalletAddressOption, onSetPrimaryWallet, onLinkNewWallet, onFromTokenChange, onToTokenChange, onConnectWallet, onAnalyticEvent: _onAnalyticEvent, onSwapSuccess, onSwapValidating, onSwapError }) => {
const onAnalyticEvent = useCallback((eventName, data) => {
try {
_onAnalyticEvent?.(eventName, data);
}
catch (e) {
console.error('Error in onAnalyticEvent', eventName, data, e);
}
}, [_onAnalyticEvent]);
const relayClient = useRelayClient();
const providerOptionsContext = useContext(ProviderOptionsContext);
const connectorKeyOverrides = providerOptionsContext.vmConnectorKeyOverrides;
const [transactionModalOpen, setTransactionModalOpen] = useState(false);
const [depositAddressModalOpen, setDepositAddressModalOpen] = useState(false);
const [addressModalOpen, setAddressModalOpen] = useState(false);
const [pendingSuccessFlush, setPendingSuccessFlush] = useState(false);
const [unverifiedTokens, setUnverifiedTokens] = useState([]);
const [isUsdInputMode, setIsUsdInputMode] = useState(false);
const [usdInputValue, setUsdInputValue] = useState('');
const [usdOutputValue, setUsdOutputValue] = useState('');
const [tokenInputCache, setTokenInputCache] = useState('');
const hasLockedToken = lockFromToken || lockToToken;
const isSingleChainLocked = singleChainMode && lockChainId !== undefined;
//Handle external unverified tokens
useEffect(() => {
if (fromToken && 'verified' in fromToken && !fromToken.verified) {
const isAlreadyAccepted = alreadyAcceptedToken(fromToken);
if (!isAlreadyAccepted) {
unverifiedTokens.push({ token: fromToken, context: 'from' });
setFromToken?.(undefined);
}
}
if (toToken && 'verified' in toToken && !toToken.verified) {
const isAlreadyAccepted = alreadyAcceptedToken(toToken);
if (!isAlreadyAccepted) {
unverifiedTokens.push({ token: toToken, context: 'to' });
setToToken?.(undefined);
}
}
}, [fromToken, toToken]);
return (_jsx(SwapWidgetRenderer, { context: "Swap", transactionModalOpen: transactionModalOpen, setTransactionModalOpen: setTransactionModalOpen, depositAddressModalOpen: depositAddressModalOpen, defaultAmount: defaultAmount, defaultToAddress: defaultToAddress, defaultTradeType: defaultTradeType, toToken: toToken, setToToken: setToToken, fromToken: fromToken, setFromToken: setFromToken, slippageTolerance: slippageTolerance, wallet: wallet, linkedWallets: linkedWallets, multiWalletSupportEnabled: multiWalletSupportEnabled, onSwapError: onSwapError, onAnalyticEvent: onAnalyticEvent, supportedWalletVMs: supportedWalletVMs, children: ({ quote, steps, swap, setSteps, feeBreakdown, fromToken, setFromToken, toToken, setToToken, error, toDisplayName, address, recipient, customToAddress, setCustomToAddress, tradeType, setTradeType, isSameCurrencySameRecipientSwap, debouncedInputAmountValue, debouncedAmountInputControls, setAmountInputValue, amountInputValue, amountOutputValue, debouncedOutputAmountValue, debouncedAmountOutputControls, setAmountOutputValue, toBalance, toBalancePending, isLoadingToBalance, isFetchingQuote, isLoadingFromBalance, fromBalance, fromBalancePending, highRelayerServiceFee, relayerFeeProportion, hasInsufficientBalance, isInsufficientLiquidityError, isCapacityExceededError, isCouldNotExecuteError, ctaCopy, isFromNative, timeEstimate, isSvmSwap, isBvmSwap, isValidFromAddress, isValidToAddress, supportsExternalLiquidity, useExternalLiquidity, slippageTolerance, canonicalTimeEstimate, fromChainWalletVMSupported, toChainWalletVMSupported, isRecipientLinked, swapError, recipientWalletSupportsChain, gasTopUpEnabled, setGasTopUpEnabled, gasTopUpRequired, gasTopUpAmount, gasTopUpAmountUsd, linkedWallet, quoteParameters, setSwapError, setUseExternalLiquidity, invalidateBalanceQueries, invalidateQuoteQuery, quoteInProgress, setQuoteInProgress, abortController, fromTokenPriceData, toTokenPriceData, isLoadingFromTokenPrice, isLoadingToTokenPrice }) => {
// Calculate the USD value of the input amount
const inputAmountUsd = useMemo(() => {
return calculateUsdValue(fromTokenPriceData?.price, amountInputValue);
}, [fromTokenPriceData, amountInputValue]);
// Calculate the USD value of the output amount
const outputAmountUsd = useMemo(() => {
return calculateUsdValue(toTokenPriceData?.price, amountOutputValue);
}, [toTokenPriceData, amountOutputValue]);
const handleMaxAmountClicked = async (amount, percent, bufferAmount) => {
if (fromToken) {
const formattedAmount = formatUnits(amount, fromToken?.decimals);
setAmountInputValue(formattedAmount);
setTradeType('EXACT_INPUT');
debouncedAmountOutputControls.cancel();
debouncedAmountInputControls.flush();
onAnalyticEvent?.(EventNames.MAX_AMOUNT_CLICKED, {
percent: percent,
bufferAmount: bufferAmount ? bufferAmount.toString() : '0',
chainType: fromChain?.vmType
});
if (isUsdInputMode && conversionRate) {
const numericTokenAmount = Number(formattedAmount);
if (!isNaN(numericTokenAmount)) {
const usdEquivalent = numericTokenAmount * conversionRate;
setUsdInputValue(usdEquivalent.toFixed(2));
}
}
}
};
const handleSetFromToken = (token) => {
if (!token) {
setFromToken(undefined);
onFromTokenChange?.(undefined);
return;
}
let _token = token;
const newFromChain = relayClient?.chains.find((chain) => token?.chainId == chain.id);
if (newFromChain?.vmType &&
!supportedWalletVMs.includes(newFromChain?.vmType)) {
setTradeType('EXACT_INPUT');
const _toToken = findBridgableToken(toChain, toToken);
if (_toToken && _toToken?.address != toToken?.address) {
handleSetToToken(_toToken);
}
const _fromToken = findBridgableToken(newFromChain, _token);
if (_fromToken && _fromToken.address != _token?.address) {
_token = _fromToken;
}
}
setFromToken(_token);
onFromTokenChange?.(_token);
};
const handleSetToToken = (token) => {
if (!token) {
setToToken(undefined);
onToTokenChange?.(undefined);
return;
}
let _token = token;
if (!fromChainWalletVMSupported) {
const newToChain = relayClient?.chains.find((chain) => token?.chainId == chain.id);
if (newToChain) {
const _toToken = findBridgableToken(newToChain, _token);
if (_toToken && _toToken.address != _token?.address) {
_token = _toToken;
}
}
}
setToToken(_token);
onToTokenChange?.(_token);
};
const fromChain = relayClient?.chains?.find((chain) => chain.id === fromToken?.chainId);
const toChain = relayClient?.chains?.find((chain) => chain.id === toToken?.chainId);
// Get public client for the fromChain to estimate gas
const publicClient = usePublicClient({ chainId: fromChain?.id });
useEffect(() => {
if (multiWalletSupportEnabled &&
fromChain &&
address &&
linkedWallets &&
!isValidFromAddress) {
const supportedAddress = findSupportedWallet(fromChain, address, linkedWallets, connectorKeyOverrides);
if (supportedAddress) {
onSetPrimaryWallet?.(supportedAddress);
}
}
if (multiWalletSupportEnabled &&
toChain &&
recipient &&
linkedWallets &&
!isValidToAddress) {
const supportedAddress = findSupportedWallet(toChain, recipient, linkedWallets, connectorKeyOverrides);
if (supportedAddress) {
setCustomToAddress(supportedAddress);
}
else {
setCustomToAddress(undefined);
}
}
}, [
multiWalletSupportEnabled,
fromChain?.id,
toChain?.id,
address,
linkedWallets,
onSetPrimaryWallet,
isValidFromAddress,
isValidToAddress,
connectorKeyOverrides
]);
//Handle if the paste wallet address option is disabled while there is a custom to address
useEffect(() => {
if (disablePasteWalletAddressOption && customToAddress) {
setCustomToAddress(undefined);
}
}, [disablePasteWalletAddressOption]);
const promptSwitchRoute = (isCapacityExceededError || isCouldNotExecuteError) &&
supportsExternalLiquidity &&
!isSingleChainLocked;
const isAutoSlippage = slippageTolerance === undefined;
const isHighPriceImpact = Number(quote?.details?.totalImpact?.percent) < -3.5;
const totalImpactUsd = quote?.details?.totalImpact?.usd;
const showHighPriceImpactWarning = Boolean(isHighPriceImpact && totalImpactUsd && Number(totalImpactUsd) <= -10);
// Calculate conversion rate
const conversionRate = useMemo(() => {
if (isUsdInputMode) {
// When in USD input mode, the conversion rate is the price of the fromToken.
if (fromTokenPriceData?.price && fromTokenPriceData.price > 0) {
return fromTokenPriceData.price;
}
else {
// If no price data, or price is 0, return null to avoid stale calculations.
return null;
}
}
else {
// When in token input mode, calculate rate from quote if available.
if (amountInputValue &&
Number(amountInputValue) > 0 &&
quote?.details?.currencyIn?.amountUsd) {
const tokenVal = Number(amountInputValue);
const usdVal = Number(quote.details.currencyIn.amountUsd);
if (tokenVal > 0 && usdVal > 0) {
const rate = usdVal / tokenVal;
return rate;
}
else {
return null;
}
}
else {
// If in token mode and token input is cleared or zero, return null
return null;
}
}
}, [
isUsdInputMode,
fromTokenPriceData?.price,
quote?.details?.currencyIn?.amountUsd,
amountInputValue
]);
// toggle between token and usd input mode
const toggleInputMode = () => {
if (!isUsdInputMode) {
// Switching TO USD mode
let newUsdInputValue = '';
let newUsdOutputValue = '';
// Calculate USD input value
if (quote?.details?.currencyIn?.amountUsd &&
Number(quote.details.currencyIn.amountUsd) > 0) {
newUsdInputValue = String(Number(quote.details.currencyIn.amountUsd));
}
else if (inputAmountUsd && inputAmountUsd > 0) {
newUsdInputValue = inputAmountUsd.toFixed(2);
}
else if (amountInputValue &&
Number(amountInputValue) > 0 &&
conversionRate &&
conversionRate > 0) {
newUsdInputValue = (Number(amountInputValue) * conversionRate).toFixed(2);
}
// Calculate USD output value
if (quote?.details?.currencyOut?.amountUsd &&
Number(quote.details.currencyOut.amountUsd) > 0) {
newUsdOutputValue = String(Number(quote.details.currencyOut.amountUsd));
}
else if (outputAmountUsd && outputAmountUsd > 0) {
newUsdOutputValue = outputAmountUsd.toFixed(2);
}
else if (amountOutputValue &&
Number(amountOutputValue) > 0 &&
toTokenPriceData?.price &&
toTokenPriceData.price > 0) {
newUsdOutputValue = (Number(amountOutputValue) * toTokenPriceData.price).toFixed(2);
}
setTokenInputCache(amountInputValue);
setUsdInputValue(newUsdInputValue);
setUsdOutputValue(newUsdOutputValue);
setIsUsdInputMode(true);
// Default to EXACT_INPUT unless we're currently in EXPECTED_OUTPUT mode with a valid USD output value
if (tradeType !== 'EXPECTED_OUTPUT' || !newUsdOutputValue) {
setTradeType('EXACT_INPUT');
}
}
else {
// Switching FROM USD mode
if (!usdInputValue && tokenInputCache) {
setAmountInputValue(tokenInputCache);
}
setUsdInputValue('');
setUsdOutputValue('');
setIsUsdInputMode(false);
// Maintain current trade type when switching back to token mode
}
};
//Update token input value when USD input changes in USD mode
useEffect(() => {
if (isUsdInputMode) {
if (conversionRate && conversionRate > 0 && usdInputValue) {
const usdValue = Number(usdInputValue);
if (!isNaN(usdValue) && usdValue > 0) {
const tokenEquivalent = (usdValue / conversionRate).toFixed(fromToken?.decimals ?? 8);
setAmountInputValue(tokenEquivalent);
}
}
else if (usdInputValue === '' || Number(usdInputValue) === 0) {
setAmountInputValue('');
}
}
}, [
isUsdInputMode,
usdInputValue,
conversionRate,
setAmountInputValue,
fromToken?.decimals
]);
//Update token output value when USD output changes in USD mode
useEffect(() => {
if (isUsdInputMode && tradeType === 'EXPECTED_OUTPUT') {
if (toTokenPriceData?.price &&
toTokenPriceData.price > 0 &&
usdOutputValue) {
const usdValue = Number(usdOutputValue);
if (!isNaN(usdValue) && usdValue > 0) {
const tokenEquivalent = (usdValue / toTokenPriceData.price).toFixed(toToken?.decimals ?? 8);
setAmountOutputValue(tokenEquivalent);
}
}
else if (usdOutputValue === '' || Number(usdOutputValue) === 0) {
setAmountOutputValue('');
}
}
}, [
isUsdInputMode,
tradeType,
usdOutputValue,
toTokenPriceData?.price,
setAmountOutputValue,
toToken?.decimals
]);
//Update USD output value when in USD mode
useEffect(() => {
if (isUsdInputMode) {
// For EXPECTED_OUTPUT, don't override user's typed value
if (tradeType === 'EXPECTED_OUTPUT') {
// User is controlling the output value directly
return;
}
// For EXACT_INPUT, update based on quote or calculations
if (quote?.details?.currencyOut?.amountUsd && !isFetchingQuote) {
// Use quote USD value when available
const quoteUsdValue = Number(quote.details.currencyOut.amountUsd);
if (!isNaN(quoteUsdValue) && quoteUsdValue >= 0) {
setUsdOutputValue(quoteUsdValue.toFixed(2));
}
}
else if (toTokenPriceData?.price &&
toTokenPriceData.price > 0 &&
amountOutputValue &&
Number(amountOutputValue) > 0) {
// Fallback to direct token price calculation
const tokenAmount = Number(amountOutputValue);
const usdEquivalent = tokenAmount * toTokenPriceData.price;
if (!isNaN(usdEquivalent) && usdEquivalent >= 0) {
setUsdOutputValue(usdEquivalent.toFixed(2));
}
}
else if (!amountOutputValue || Number(amountOutputValue) === 0) {
setUsdOutputValue('');
}
}
}, [
isUsdInputMode,
tradeType,
quote?.details?.currencyOut?.amountUsd,
isFetchingQuote,
toTokenPriceData?.price,
amountOutputValue
]);
//Update USD input value when in EXPECTED_OUTPUT mode
useEffect(() => {
if (isUsdInputMode && tradeType === 'EXPECTED_OUTPUT') {
if (quote?.details?.currencyIn?.amountUsd && !isFetchingQuote) {
// Use quote USD value when available
const quoteUsdValue = Number(quote.details.currencyIn.amountUsd);
if (!isNaN(quoteUsdValue) && quoteUsdValue >= 0) {
setUsdInputValue(quoteUsdValue.toFixed(2));
}
}
else if (!amountInputValue || Number(amountInputValue) === 0) {
setUsdInputValue('');
}
}
}, [
isUsdInputMode,
tradeType,
quote?.details?.currencyIn?.amountUsd,
isFetchingQuote,
amountInputValue
]);
const recipientLinkedWallet = linkedWallets?.find((wallet) => wallet.address === recipient);
return (_jsxs(_Fragment, { children: [_jsx(WidgetContainer, { steps: steps, setSteps: setSteps, quoteInProgress: quoteInProgress, setQuoteInProgress: setQuoteInProgress, transactionModalOpen: transactionModalOpen, setTransactionModalOpen: setTransactionModalOpen, depositAddressModalOpen: depositAddressModalOpen, setDepositAddressModalOpen: setDepositAddressModalOpen, addressModalOpen: addressModalOpen, setAddressModalOpen: setAddressModalOpen, fromToken: fromToken, fromChain: fromChain, toToken: toToken, toChain: toChain, address: address, recipient: recipient, amountInputValue: amountInputValue, amountOutputValue: amountOutputValue, debouncedInputAmountValue: debouncedInputAmountValue, debouncedOutputAmountValue: debouncedOutputAmountValue, tradeType: tradeType, onTransactionModalOpenChange: (open) => {
if (!open) {
if (pendingSuccessFlush) {
setPendingSuccessFlush(false);
}
else if (steps) {
invalidateQuoteQuery();
}
// Abort ongoing execution
if (abortController) {
abortController.abort();
}
setSwapError(null);
setSteps(null);
setQuoteInProgress(null);
}
else if (pendingSuccessFlush) {
setPendingSuccessFlush(false);
}
}, onDepositAddressModalOpenChange: (open) => {
if (!open) {
setSwapError(null);
if (pendingSuccessFlush) {
setPendingSuccessFlush(false);
}
else {
invalidateQuoteQuery();
}
}
else if (pendingSuccessFlush) {
setPendingSuccessFlush(false);
}
}, useExternalLiquidity: useExternalLiquidity, slippageTolerance: slippageTolerance, swapError: swapError, setSwapError: setSwapError, onSwapSuccess: (data) => {
setPendingSuccessFlush(true);
setGasTopUpEnabled(true);
setAmountInputValue('');
setAmountOutputValue('');
onSwapSuccess?.(data);
}, onSwapValidating: onSwapValidating, onAnalyticEvent: onAnalyticEvent, invalidateBalanceQueries: invalidateBalanceQueries, invalidateQuoteQuery: invalidateQuoteQuery, customToAddress: customToAddress, setCustomToAddress: setCustomToAddress, timeEstimate: timeEstimate, wallet: wallet, linkedWallets: linkedWallets, multiWalletSupportEnabled: multiWalletSupportEnabled, children: () => {
return (_jsxs(Flex, { direction: "column", css: {
width: '100%',
overflow: 'hidden',
border: 'widget-border',
minWidth: 300,
maxWidth: 408
}, children: [_jsxs(TokenSelectorContainer, { css: { backgroundColor: 'widget-background' }, id: 'from-token-section', children: [_jsxs(Flex, { align: "center", justify: "between", css: { gap: '2', width: '100%' }, children: [_jsx(Text, { style: "subtitle2", color: "subtle", children: "Sell" }), multiWalletSupportEnabled === true &&
fromChainWalletVMSupported ? (_jsx(MultiWalletDropdown, { context: "origin", selectedWalletAddress: address, disablePasteWalletAddressOption: disablePasteWalletAddressOption, onSelect: (wallet) => onSetPrimaryWallet?.(wallet.address), chain: fromChain, onLinkNewWallet: () => {
if (!address && fromChainWalletVMSupported) {
onConnectWallet?.();
}
else {
onLinkNewWallet?.({
chain: fromChain,
direction: 'from'
})?.then((wallet) => {
onSetPrimaryWallet?.(wallet.address);
});
}
}, setAddressModalOpen: setAddressModalOpen, wallets: linkedWallets, onAnalyticEvent: onAnalyticEvent })) : null] }), _jsxs(Flex, { align: "center", justify: "between", css: { gap: '4', width: '100%' }, children: [_jsx(AmountInput, { autoFocus: !disableInputAutoFocus, prefixSymbol: isUsdInputMode ? '$' : undefined, value: isUsdInputMode
? usdInputValue
: tradeType === 'EXACT_INPUT'
? amountInputValue
: amountInputValue
? formatFixedLength(amountInputValue, 8)
: amountInputValue, setValue: (e) => {
if (isUsdInputMode) {
setUsdInputValue(e);
setTradeType('EXACT_INPUT');
setTokenInputCache('');
if (Number(e) === 0) {
setAmountOutputValue('');
setUsdOutputValue('');
debouncedAmountInputControls.flush();
}
}
else {
setAmountInputValue(e);
setTradeType('EXACT_INPUT');
if (Number(e) === 0) {
setAmountOutputValue('');
debouncedAmountInputControls.flush();
}
}
}, onFocus: () => {
onAnalyticEvent?.(EventNames.SWAP_INPUT_FOCUSED);
}, css: {
fontWeight: '700',
fontSize: 32,
lineHeight: '36px',
py: 0,
color: isFetchingQuote && tradeType === 'EXPECTED_OUTPUT'
? 'text-subtle'
: 'input-color',
_placeholder: {
color: isFetchingQuote &&
tradeType === 'EXPECTED_OUTPUT'
? 'text-subtle'
: 'input-color'
}
} }), _jsx(TokenSelector, { address: address, isValidAddress: isValidFromAddress, token: fromToken, onAnalyticEvent: onAnalyticEvent, fromChainWalletVMSupported: fromChainWalletVMSupported, supportedWalletVMs: supportedWalletVMs, setToken: (token) => {
if (token.address === toToken?.address &&
token.chainId === toToken?.chainId &&
address === recipient &&
(!lockToToken || !fromToken)) {
handleSetFromToken(toToken);
handleSetToToken(fromToken);
}
else {
handleSetFromToken(token);
}
}, context: "from", multiWalletSupportEnabled: multiWalletSupportEnabled, lockedChainIds: isSingleChainLocked
? [lockChainId]
: isChainLocked(fromToken?.chainId, lockChainId, toToken?.chainId, lockFromToken) && fromToken?.chainId
? [fromToken.chainId]
: undefined, chainIdsFilter: !fromChainWalletVMSupported && toToken
? [toToken.chainId]
: undefined, popularChainIds: popularChainIds, trigger: _jsx("div", { style: { width: 'max-content' }, children: _jsx(TokenTrigger, { token: fromToken, locked: lockFromToken, isSingleChainLocked: isSingleChainLocked, address: address }) }) })] }), _jsxs(Flex, { align: "center", justify: "between", css: { gap: '3', width: '100%' }, children: [_jsxs(Flex, { align: "center", css: { gap: '4px', _hover: { cursor: 'pointer' } }, onClick: () => {
toggleInputMode();
}, children: [_jsx(Text, { style: "subtitle3", color: "subtleSecondary", css: {
minHeight: 18,
display: 'flex',
alignItems: 'center'
}, children: isUsdInputMode ? (fromToken ? (
// In USD input mode, show token equivalent
usdInputValue && Number(usdInputValue) > 0 ? (
// USD input has a value
amountInputValue &&
conversionRate &&
!isLoadingFromTokenPrice ? (`${formatNumber(amountInputValue, 4, false)} ${fromToken.symbol}`) : (_jsx(Box, { css: {
width: 45,
height: 12,
backgroundColor: 'gray7',
borderRadius: 'widget-border-radius'
} }))) : (
// USD input is empty or zero, show "0 TOKEN_SYMBOL"
`0 ${fromToken.symbol}`)) : null) : quote?.details?.currencyIn?.amountUsd &&
!isFetchingQuote ? (
// In token input mode, show USD equivalent from quote
formatDollar(Number(quote.details.currencyIn.amountUsd))) : isLoadingFromTokenPrice && // This is for the direct fromToken price, used when quote isn't available yet
amountInputValue &&
Number(amountInputValue) > 0 ? (_jsx(Box, { css: {
width: 45,
height: 12,
backgroundColor: 'gray7',
borderRadius: 'widget-border-radius'
} })) : inputAmountUsd &&
inputAmountUsd > 0 &&
fromTokenPriceData?.price &&
fromTokenPriceData.price > 0 ? (formatDollar(inputAmountUsd)) : ('$0.00') }), _jsx(Button, { "aria-label": "Switch Input Mode", size: "none", color: "ghost", css: {
color: 'gray11',
alignSelf: 'center',
justifyContent: 'center',
width: '20px',
height: '20px',
borderRadius: '100px',
padding: '4px',
backgroundColor: 'gray3'
}, onClick: toggleInputMode, children: _jsx(SwitchIcon, { width: 16, height: 10 }) })] }), _jsxs(Flex, { align: "center", css: { gap: '3', marginLeft: 'auto', height: 23 }, children: [fromToken ? (_jsx(BalanceDisplay, { isLoading: isLoadingFromBalance, balance: fromBalance, decimals: fromToken?.decimals, symbol: fromToken?.symbol, hasInsufficientBalance: hasInsufficientBalance, displaySymbol: false, isConnected: !isDeadAddress(address) &&
address !== tronDeadAddress &&
address !== undefined, pending: fromBalancePending })) : (_jsx(Flex, { css: { height: 18 } })), fromBalance &&
(fromChain?.vmType === 'evm' || // EVM
fromChain?.vmType === 'svm') ? (_jsxs(Flex, { css: { gap: '1' }, children: [_jsx(Button, { "aria-label": "20%", css: {
fontSize: 12,
fontWeight: '500',
px: '1',
py: '1',
minHeight: '23px',
lineHeight: '100%',
backgroundColor: 'widget-selector-background',
border: 'none',
_hover: {
backgroundColor: 'widget-selector-hover-background'
}
}, color: "white", onClick: () => {
const percentageBuffer = (fromBalance * 20n) / 100n; // 20% of the balance
handleMaxAmountClicked(percentageBuffer, '20%');
}, children: "20%" }), _jsx(Button, { "aria-label": "50%", css: {
fontSize: 12,
fontWeight: '500',
px: '1',
py: '1',
minHeight: '23px',
lineHeight: '100%',
backgroundColor: 'widget-selector-background',
border: 'none',
_hover: {
backgroundColor: 'widget-selector-hover-background'
}
}, color: "white", onClick: () => {
const percentageBuffer = (fromBalance * 50n) / 100n; // 50% of the balance
handleMaxAmountClicked(percentageBuffer, '50%');
}, children: "50%" }), _jsx(Button, { "aria-label": "MAX", css: {
fontSize: 12,
fontWeight: '500',
px: '1',
py: '1',
minHeight: '23px',
lineHeight: '100%',
backgroundColor: 'widget-selector-background',
border: 'none',
_hover: {
backgroundColor: 'widget-selector-hover-background'
}
}, color: "white", onMouseEnter: () => {
if (fromChain?.vmType === 'evm' &&
publicClient &&
fromBalance) {
getFeeBufferAmount(fromChain.vmType, fromChain.id, fromBalance, publicClient);
}
else if (fromChain?.vmType === 'svm' &&
fromChain.id) {
getFeeBufferAmount(fromChain.vmType, fromChain.id, 0n, null);
}
}, onClick: async () => {
if (!fromBalance || !fromToken || !fromChain)
return;
let feeBufferAmount = 0n;
if (isFromNative) {
feeBufferAmount = await getFeeBufferAmount(fromChain.vmType, fromChain.id, fromBalance, publicClient ?? null);
}
const finalMaxAmount = isFromNative && feeBufferAmount > 0n
? fromBalance > feeBufferAmount
? fromBalance - feeBufferAmount
: 0n
: fromBalance;
handleMaxAmountClicked(finalMaxAmount, 'max', isFromNative ? feeBufferAmount : 0n);
}, children: "MAX" })] })) : null] })] })] }), _jsx(Box, { css: {
position: 'relative',
my: -13,
mx: 'auto',
height: 32,
width: 32
}, children: hasLockedToken ||
((isSvmSwap || isBvmSwap) &&
!multiWalletSupportEnabled) ? null : (_jsx(Button, { "aria-label": "Swap Tokens Direction", size: "none", color: "white", css: {
mt: '4px',
color: 'gray9',
alignSelf: 'center',
justifyContent: 'center',
width: '100%',
height: '100%',
'--borderWidth': 'borders.widget-swap-currency-button-border-width',
'--borderColor': 'colors.widget-swap-currency-button-border-color',
border: `var(--borderWidth) solid var(--borderColor)`,
zIndex: 10,
borderRadius: 'widget-swap-currency-button-border-radius'
}, onClick: () => {
if (fromToken || toToken) {
if (isUsdInputMode) {
// In USD mode, switch the tokens and values
handleSetFromToken(toToken);
handleSetToToken(fromToken);
// Switch USD values
const tempUsdInput = usdInputValue;
setUsdInputValue(usdOutputValue);
setUsdOutputValue(tempUsdInput);
// Always use EXACT_INPUT after switching
setTradeType('EXACT_INPUT');
debouncedAmountInputControls.flush();
debouncedAmountOutputControls.flush();
}
else {
// Token-denominated mode: maintain current behaviour when swapping token order
if (tradeType === 'EXACT_INPUT') {
setTradeType('EXPECTED_OUTPUT');
setAmountInputValue('');
setAmountOutputValue(amountInputValue);
}
else {
setTradeType('EXACT_INPUT');
setAmountOutputValue('');
setAmountInputValue(amountOutputValue);
}
handleSetFromToken(toToken);
handleSetToToken(fromToken);