diff --git a/api/src/api/services/phases/handlers/brla-teleport-handler.ts b/api/src/api/services/phases/handlers/brla-teleport-handler.ts index c99bbec0f..cb3070fe6 100644 --- a/api/src/api/services/phases/handlers/brla-teleport-handler.ts +++ b/api/src/api/services/phases/handlers/brla-teleport-handler.ts @@ -63,7 +63,7 @@ export class BrlaTeleportPhaseHandler extends BasePhaseHandler { // Add delay to ensure the transaction is settled await new Promise((resolve) => setTimeout(resolve, 12000)); // 12 seconds, 2 moonbeam blocks. - + } catch (balanceCheckError) { if (balanceCheckError instanceof Error) { if (balanceCheckError.message === 'Balance did not meet the limit within the specified time') { diff --git a/api/src/api/services/ramp/quote.service.ts b/api/src/api/services/ramp/quote.service.ts index 246c23aee..a5ee57fca 100644 --- a/api/src/api/services/ramp/quote.service.ts +++ b/api/src/api/services/ramp/quote.service.ts @@ -22,14 +22,14 @@ import { calculateTotalReceive, calculateTotalReceiveOnramp } from '../../helper import { createOnrampRouteParams, getRoute } from '../transactions/squidrouter/route'; import { parseContractBalanceResponse, stringifyBigWithSignificantDecimals } from '../../helpers/contracts'; import { MOONBEAM_EPHEMERAL_STARTING_BALANCE_UNITS, MOONBEAM_EPHEMERAL_STARTING_BALANCE_UNITS_ETHEREUM } from '../../../constants/constants'; -import { multiplyByPowerOfTen } from '../../services/pendulum/helpers'; +import { multiplyByPowerOfTen } from "../pendulum/helpers"; /** * Trims trailing zeros from a decimal string, keeping at least two decimal places. * @param decimalString - The decimal string to format * @returns Formatted string with unnecessary trailing zeros removed but at least two decimal places */ function trimTrailingZeros(decimalString: string): string { - if (!decimalString.includes('.')) { + if (!decimalString?.includes('.')) { return `${decimalString}.00`; } diff --git a/frontend/src/components/AssetNumericInput/index.tsx b/frontend/src/components/AssetNumericInput/index.tsx index c22af4810..13293c1b9 100644 --- a/frontend/src/components/AssetNumericInput/index.tsx +++ b/frontend/src/components/AssetNumericInput/index.tsx @@ -12,6 +12,7 @@ interface AssetNumericInputProps { onChange?: (e: ChangeEvent) => void; disabled?: boolean; readOnly?: boolean; + loading?: boolean; registerInput: UseFormRegisterReturn; id: string; } @@ -35,10 +36,14 @@ export const AssetNumericInput: FC = ({ - + {rest.loading ? ( +
+ ) : ( + + )} ); diff --git a/frontend/src/components/Ramp/Offramp/index.tsx b/frontend/src/components/Ramp/Offramp/index.tsx index f87d34ed7..b243ac52b 100644 --- a/frontend/src/components/Ramp/Offramp/index.tsx +++ b/frontend/src/components/Ramp/Offramp/index.tsx @@ -24,6 +24,7 @@ import { RampSubmitButtons } from '../../RampSubmitButtons'; import { useQuoteService } from '../../../hooks/ramp/useQuoteService'; import { useTranslation } from 'react-i18next'; import { useInitializeFailedMessage } from '../../../stores/rampStore'; +import { useQuoteLoading } from '../../../stores/ramp/useQuoteStore'; export const Offramp = () => { const { t } = useTranslation(); @@ -37,6 +38,8 @@ export const Offramp = () => { const debouncedInputAmount = useDebouncedValue(inputAmount, 1000); const { outputAmount: toAmount } = useQuoteService(debouncedInputAmount, onChainToken, fiatToken); + const quoteLoading = useQuoteLoading(); + // TODO: This is a hack to get the output amount to the form useEffect(() => { form.setValue('outputAmount', toAmount?.toString() || '0'); @@ -96,13 +99,14 @@ export const Offramp = () => { assetIcon={toToken.fiat.assetIcon} tokenSymbol={toToken.fiat.symbol} onClick={() => openTokenSelectModal('to')} + loading={quoteLoading} registerInput={form.register('outputAmount')} disabled={!toAmount} readOnly={true} id="outputAmount" /> ), - [toToken.fiat.assetIcon, toToken.fiat.symbol, form, toAmount, openTokenSelectModal], + [toToken.fiat.assetIcon, toToken.fiat.symbol, quoteLoading, form, toAmount, openTokenSelectModal], ); const handleConfirm = useCallback(() => { diff --git a/frontend/src/components/Ramp/Onramp/index.tsx b/frontend/src/components/Ramp/Onramp/index.tsx index ce4e633f1..3ada29130 100644 --- a/frontend/src/components/Ramp/Onramp/index.tsx +++ b/frontend/src/components/Ramp/Onramp/index.tsx @@ -23,6 +23,7 @@ import { useInputAmount, useOnChainToken, useFiatToken } from '../../../stores/r import { RampFeeCollapse } from '../../RampFeeCollapse'; import { RampSubmitButtons } from '../../RampSubmitButtons'; import { useInitializeFailedMessage } from '../../../stores/rampStore'; +import { useQuoteLoading } from '../../../stores/ramp/useQuoteStore'; export const Onramp = () => { const { t } = useTranslation(); @@ -33,6 +34,7 @@ export const Onramp = () => { const inputAmount = useInputAmount(); const onChainToken = useOnChainToken(); const fiatToken = useFiatToken(); + const quoteLoading = useQuoteLoading(); const debouncedInputAmount = useDebouncedValue(inputAmount, 1000); @@ -95,12 +97,13 @@ export const Onramp = () => { tokenSymbol={toToken.assetSymbol} onClick={() => openTokenSelectModal('to')} registerInput={form.register('outputAmount')} + loading={quoteLoading} disabled={!toAmount} readOnly={true} id="outputAmount" /> ), - [toToken, form, toAmount, openTokenSelectModal], + [toToken.networkAssetIcon, toToken.assetSymbol, form, quoteLoading, toAmount, openTokenSelectModal], ); const handleConfirm = useCallback(() => { diff --git a/frontend/src/components/RampSubmitButtons/index.tsx b/frontend/src/components/RampSubmitButtons/index.tsx index 476264557..95ce7dafd 100644 --- a/frontend/src/components/RampSubmitButtons/index.tsx +++ b/frontend/src/components/RampSubmitButtons/index.tsx @@ -10,6 +10,8 @@ import { useRampDirection } from '../../stores/rampDirectionStore'; import { RampDirection } from '../RampToggle'; import { useTranslation } from 'react-i18next'; import { useWidgetMode } from '../../hooks/useWidgetMode'; +import { useQuoteStore } from '../../stores/ramp/useQuoteStore'; + interface RampSubmitButtonsProps { toAmount?: Big; @@ -24,12 +26,16 @@ export const RampSubmitButtons: FC = ({ toAmount }) => { const executionInput = useRampExecutionInput() const initializeFailedMessage = useInitializeFailedMessage(); const isRampSummaryDialogVisible = useRampSummaryVisible(); - const inputAmount = useInputAmount(); const fiatToken = useFiatToken(); const onChainToken = useOnChainToken(); const rampDirection = useRampDirection(); const isWidgetMode = useWidgetMode(); + + const inputAmount = useInputAmount(); + const { quote } = useQuoteStore(); + const quoteInputAmount = quote?.inputAmount; + const handleCompareFeesClick = useCallback( (e: React.MouseEvent) => { e.preventDefault(); @@ -55,8 +61,9 @@ export const RampSubmitButtons: FC = ({ toAmount }) => { return t('components.swapSubmitButton.confirm'); }; - const isSubmitButtonDisabled = Boolean(getCurrentErrorMessage()) || !toAmount || !!initializeFailedMessage; - const isSubmitButtonPending = isRampSummaryDialogVisible || Boolean(executionInput); + const isQuoteOutdated = !!quoteInputAmount && !!inputAmount && !(Big(quoteInputAmount).eq(Big(inputAmount))) + const isSubmitButtonDisabled = Boolean(getCurrentErrorMessage()) || !toAmount || !!initializeFailedMessage || isQuoteOutdated; + const isSubmitButtonPending = isRampSummaryDialogVisible || Boolean(executionInput) || isQuoteOutdated; return (
diff --git a/frontend/src/hooks/ramp/useRampValidation.ts b/frontend/src/hooks/ramp/useRampValidation.ts index 4b7f57a57..6a0e3c836 100644 --- a/frontend/src/hooks/ramp/useRampValidation.ts +++ b/frontend/src/hooks/ramp/useRampValidation.ts @@ -136,7 +136,7 @@ function validateOfframp( function validateTokenAvailability( t: TFunction<'translation', undefined>, fiatToken: FiatToken, - trackEvent: (event: TrackableEvent) => void + trackEvent: (event: TrackableEvent) => void, ): string | null { if (isFiatTokenDisabled(fiatToken)) { const reason = getTokenDisabledReason(fiatToken); @@ -144,7 +144,7 @@ function validateTokenAvailability( event: 'token_unavailable', token: fiatToken, }); - return t(reason) + return t(reason); } return null; } @@ -201,13 +201,11 @@ export const useRampValidation = () => { } if (validationError) return validationError; - if (quoteLoading) return t('components.swap.validation.calculatingQuote'); return null; }, [ isDisconnected, isOnramp, - quoteLoading, t, inputAmount, fromToken,