diff --git a/apps/frontend/App.css b/apps/frontend/App.css index 09b6595c6..1f003454e 100644 --- a/apps/frontend/App.css +++ b/apps/frontend/App.css @@ -6,7 +6,7 @@ --color-primary-content: #fff; --color-secondary: #f4f5f6; --color-secondary-content: #58667e; - --color-accent: #1de7df; + --color-accent: #db2777; --color-accent-content: #000; --color-neutral: #eff2f5; --color-neutral-content: #58667e; diff --git a/apps/frontend/android-chrome-192x192.png b/apps/frontend/android-chrome-192x192.png new file mode 100644 index 000000000..0a6776adc Binary files /dev/null and b/apps/frontend/android-chrome-192x192.png differ diff --git a/apps/frontend/android-chrome-512x512.png b/apps/frontend/android-chrome-512x512.png new file mode 100644 index 000000000..e4f9a4c82 Binary files /dev/null and b/apps/frontend/android-chrome-512x512.png differ diff --git a/apps/frontend/apple-touch-icon.png b/apps/frontend/apple-touch-icon.png new file mode 100644 index 000000000..47ea6e828 Binary files /dev/null and b/apps/frontend/apple-touch-icon.png differ diff --git a/apps/frontend/favicon-16x16.png b/apps/frontend/favicon-16x16.png new file mode 100644 index 000000000..8bdbb9e07 Binary files /dev/null and b/apps/frontend/favicon-16x16.png differ diff --git a/apps/frontend/favicon-32x32.png b/apps/frontend/favicon-32x32.png new file mode 100644 index 000000000..880c99412 Binary files /dev/null and b/apps/frontend/favicon-32x32.png differ diff --git a/apps/frontend/favicon.ico b/apps/frontend/favicon.ico new file mode 100644 index 000000000..b02003be1 Binary files /dev/null and b/apps/frontend/favicon.ico differ diff --git a/apps/frontend/favicon.png b/apps/frontend/favicon.png deleted file mode 100644 index 6f0f78240..000000000 Binary files a/apps/frontend/favicon.png and /dev/null differ diff --git a/apps/frontend/index.html b/apps/frontend/index.html index 030b08de6..7538c5a9e 100644 --- a/apps/frontend/index.html +++ b/apps/frontend/index.html @@ -2,7 +2,11 @@ - + + + + + Vortex diff --git a/apps/frontend/site.webmanifest b/apps/frontend/site.webmanifest new file mode 100644 index 000000000..085af57de --- /dev/null +++ b/apps/frontend/site.webmanifest @@ -0,0 +1,19 @@ +{ + "background_color": "#f5f9fa", + "display": "standalone", + "icons": [ + { + "sizes": "192x192", + "src": "/frontend/android-chrome-192x192.png", + "type": "image/png" + }, + { + "sizes": "512x512", + "src": "/frontend/android-chrome-512x512.png", + "type": "image/png" + } + ], + "name": "Vortex", + "short_name": "Vortex", + "theme_color": "#0f4dc0" +} diff --git a/apps/frontend/src/components/Avenia/AveniaField/index.tsx b/apps/frontend/src/components/Avenia/AveniaField/index.tsx index 297710cd3..207c6d3b3 100644 --- a/apps/frontend/src/components/Avenia/AveniaField/index.tsx +++ b/apps/frontend/src/components/Avenia/AveniaField/index.tsx @@ -1,3 +1,4 @@ +import { CalendarDaysIcon } from "@heroicons/react/24/outline"; import { motion } from "motion/react"; import { FC } from "react"; import { useFormContext, useFormState } from "react-hook-form"; @@ -69,21 +70,12 @@ export const AveniaField: FC = ({ id, label, index, validation - +
+ + {id === ExtendedAveniaFieldOptions.BIRTHDATE && ( + + )} +
{errorMessage && {errorMessage}} ); diff --git a/apps/frontend/src/components/Avenia/DocumentUpload/index.tsx b/apps/frontend/src/components/Avenia/DocumentUpload/index.tsx index e2f674c85..e961280a3 100644 --- a/apps/frontend/src/components/Avenia/DocumentUpload/index.tsx +++ b/apps/frontend/src/components/Avenia/DocumentUpload/index.tsx @@ -161,11 +161,13 @@ export const DocumentUpload: React.FC = ({ aveniaKycActor, label: string, onChange: React.ChangeEventHandler | undefined, valid: boolean, - Icon: React.ComponentType> + Icon: React.ComponentType>, + fileName?: string ) => ( @@ -190,13 +192,15 @@ export const DocumentUpload: React.FC = ({ aveniaKycActor, t("components.documentUpload.fields.rgFront"), e => handleFileChange(e, setFront, setFrontValid), frontValid, - DocumentTextIcon + DocumentTextIcon, + front?.name )} {renderField( t("components.documentUpload.fields.rgBack"), e => handleFileChange(e, setBack, setBackValid), backValid, - DocumentTextIcon + DocumentTextIcon, + back?.name )} )} @@ -205,7 +209,8 @@ export const DocumentUpload: React.FC = ({ aveniaKycActor, t("components.documentUpload.fields.cnhDocument"), e => handleFileChange(e, setFront, setFrontValid), frontValid, - DocumentTextIcon + DocumentTextIcon, + front?.name )} diff --git a/apps/frontend/src/components/QuoteSubmitButtons/index.tsx b/apps/frontend/src/components/QuoteSubmitButtons/index.tsx index b2916c480..cb3ac4fd5 100644 --- a/apps/frontend/src/components/QuoteSubmitButtons/index.tsx +++ b/apps/frontend/src/components/QuoteSubmitButtons/index.tsx @@ -106,7 +106,7 @@ export const QuoteSubmitButton: FC = ({ className, disab return (
diff --git a/apps/frontend/src/components/RampSubmitButton/RampSubmitButton.tsx b/apps/frontend/src/components/RampSubmitButton/RampSubmitButton.tsx index 4f33d2c1c..ea8691ba8 100644 --- a/apps/frontend/src/components/RampSubmitButton/RampSubmitButton.tsx +++ b/apps/frontend/src/components/RampSubmitButton/RampSubmitButton.tsx @@ -177,7 +177,7 @@ const useButtonContent = ({ toToken, submitButtonDisabled }: UseButtonContentPro ]); }; -export const RampSubmitButton = ({ className }: { className?: string }) => { +export const RampSubmitButton = ({ className, hasValidationErrors }: { className?: string; hasValidationErrors?: boolean }) => { const rampActor = useRampActor(); const { onRampConfirm } = useRampSubmission(); const stellarData = useStellarKycSelector(); @@ -208,6 +208,10 @@ export const RampSubmitButton = ({ className }: { className?: string }) => { const toToken = isOnramp ? getOnChainTokenDetailsOrDefault(selectedNetwork, onChainToken) : getAnyFiatTokenDetails(fiatToken); const submitButtonDisabled = useMemo(() => { + if (hasValidationErrors) { + return true; + } + if ( walletLocked && (isOfframp || quote?.from === "sepa") && @@ -242,6 +246,7 @@ export const RampSubmitButton = ({ className }: { className?: string }) => { return false; }, [ + hasValidationErrors, executionInput, isQuoteExpired, isOfframp, diff --git a/apps/frontend/src/components/widget-steps/DetailsStep/DetailsStepActions.tsx b/apps/frontend/src/components/widget-steps/DetailsStep/DetailsStepActions.tsx index 0f8a38039..5c9cb283a 100644 --- a/apps/frontend/src/components/widget-steps/DetailsStep/DetailsStepActions.tsx +++ b/apps/frontend/src/components/widget-steps/DetailsStep/DetailsStepActions.tsx @@ -1,4 +1,6 @@ import { Networks } from "@vortexfi/shared"; +import { useFormContext } from "react-hook-form"; +import { RampFormValues } from "../../../hooks/ramp/schema"; import { useVortexAccount } from "../../../hooks/useVortexAccount"; import { ConnectWalletSection } from "../../ConnectWalletSection"; import { RampSubmitButton } from "../../RampSubmitButton/RampSubmitButton"; @@ -10,12 +12,38 @@ export interface DetailsStepActionsProps { requiresConnection: boolean; className?: string; forceNetwork?: Networks; + isBrazilLanding: boolean; } -export const DetailsStepActions = ({ signingState, className, requiresConnection, forceNetwork }: DetailsStepActionsProps) => { +export const DetailsStepActions = ({ + signingState, + className, + requiresConnection, + forceNetwork, + isBrazilLanding +}: DetailsStepActionsProps) => { const { shouldDisplay: signingBoxVisible, signatureState, confirmations } = signingState; const { isConnected } = useVortexAccount(forceNetwork); + const { + formState: { errors }, + watch + } = useFormContext(); + const formValues = watch(); + + const hasFormErrors = Object.keys(errors).length > 0; + + let hasEmptyForm = false; + + if (isBrazilLanding) { + const allRelevantFieldsEmpty = !formValues.taxId || !formValues.walletAddress; + hasEmptyForm = allRelevantFieldsEmpty; + } else { + hasEmptyForm = !formValues.walletAddress; + } + + const hasValidationErrors = hasFormErrors || hasEmptyForm; + if (signingBoxVisible) { return (
@@ -28,7 +56,7 @@ export const DetailsStepActions = ({ signingState, className, requiresConnection return (
{requiresConnection && } - {displayRampSubmitButton && } + {displayRampSubmitButton && }
); }; diff --git a/apps/frontend/src/components/widget-steps/DetailsStep/index.tsx b/apps/frontend/src/components/widget-steps/DetailsStep/index.tsx index fdbfd6d2f..a2291a00a 100644 --- a/apps/frontend/src/components/widget-steps/DetailsStep/index.tsx +++ b/apps/frontend/src/components/widget-steps/DetailsStep/index.tsx @@ -1,10 +1,11 @@ import { InformationCircleIcon } from "@heroicons/react/24/outline"; import { FiatToken, Networks } from "@vortexfi/shared"; import { useSelector } from "@xstate/react"; -import { useEffect } from "react"; +import { useEffect, useRef } from "react"; import { FormProvider } from "react-hook-form"; import { useTranslation } from "react-i18next"; import { useRampActor } from "../../../contexts/rampState"; +import { RampFormValues } from "../../../hooks/ramp/schema"; import { useRampForm } from "../../../hooks/ramp/useRampForm"; import { useRampSubmission } from "../../../hooks/ramp/useRampSubmission"; import { useSigningBoxState } from "../../../hooks/useSigningBoxState"; @@ -32,6 +33,7 @@ export interface FormData { taxId?: string; moneriumWalletAddress?: string; walletAddress?: string; + fiatToken?: FiatToken; } export const DetailsStep = ({ className }: DetailsStepProps) => { @@ -58,6 +60,7 @@ export const DetailsStep = ({ className }: DetailsStepProps) => { const walletForm = walletLockedFromState || address || undefined; const { form } = useRampForm({ + fiatToken: quote?.rampType === "BUY" ? (quote.inputCurrency as FiatToken) : (quote?.outputCurrency as FiatToken), moneriumWalletAddress: evmAddress, pixId, taxId, @@ -72,12 +75,18 @@ export const DetailsStep = ({ className }: DetailsStepProps) => { if (isMoneriumToAssethubRamp && substrateAddress) { form.setValue("walletAddress", substrateAddress); - } else if (walletLockedFromState) { - form.setValue("walletAddress", walletLockedFromState); } else if (!isMoneriumToAssethubRamp && address) { form.setValue("walletAddress", address); + } else if (walletLockedFromState) { + form.setValue("walletAddress", walletLockedFromState); } - }, [form, evmAddress, isMoneriumRamp, address, walletLockedFromState, isMoneriumToAssethubRamp, substrateAddress]); + + const fiatToken = quote?.rampType === "BUY" ? (quote.inputCurrency as FiatToken) : (quote?.outputCurrency as FiatToken); + form.setValue("fiatToken", fiatToken); + }, [form, evmAddress, isMoneriumRamp, address, walletLockedFromState, isMoneriumToAssethubRamp, substrateAddress, quote]); + + const previousValues = useRef({}); + const currentValues = form.watch(); const { onRampConfirm } = useRampSubmission(); @@ -118,7 +127,12 @@ export const DetailsStep = ({ className }: DetailsStepProps) => {
)} - + diff --git a/apps/frontend/src/hooks/brla/useKYCForm/index.tsx b/apps/frontend/src/hooks/brla/useKYCForm/index.tsx index 47d881848..ca90fa158 100644 --- a/apps/frontend/src/hooks/brla/useKYCForm/index.tsx +++ b/apps/frontend/src/hooks/brla/useKYCForm/index.tsx @@ -60,12 +60,19 @@ const createKycFormSchema = (t: (key: string) => string) => [ExtendedAveniaFieldOptions.BIRTHDATE]: yup .date() - .transform((value, originalValue) => { + .transform((value: Date | undefined, originalValue: any) => { return originalValue === "" ? undefined : value; }) .required(t("components.brlaExtendedForm.validation.birthdate.required")) .max(new Date(), t("components.brlaExtendedForm.validation.birthdate.future")) - .min(new Date(1900, 0, 1), t("components.brlaExtendedForm.validation.birthdate.tooOld")), + .min(new Date(1900, 0, 1), t("components.brlaExtendedForm.validation.birthdate.tooOld")) + .test("is-18-or-older", t("components.brlaExtendedForm.validation.birthdate.tooYoung"), value => { + if (!value) return true; + const birthDate = new Date(value); + const ageDate = new Date(birthDate); + ageDate.setFullYear(ageDate.getFullYear() + 18); + return ageDate <= new Date(); + }), [ExtendedAveniaFieldOptions.COMPANY_NAME]: yup .string() @@ -73,7 +80,7 @@ const createKycFormSchema = (t: (key: string) => string) => [ExtendedAveniaFieldOptions.START_DATE]: yup .date() - .transform((value, originalValue) => { + .transform((value: Date | undefined, originalValue: any) => { return originalValue === "" ? undefined : value; }) .max(new Date(), t("components.brlaExtendedForm.validation.startDate.future")) diff --git a/apps/frontend/src/hooks/ramp/schema.ts b/apps/frontend/src/hooks/ramp/schema.ts index 59e772fba..106a4931a 100644 --- a/apps/frontend/src/hooks/ramp/schema.ts +++ b/apps/frontend/src/hooks/ramp/schema.ts @@ -11,6 +11,7 @@ export type RampFormValues = { pixId?: string; walletAddress?: string; moneriumWalletAddress?: string; + fiatToken?: FiatToken; }; export const PHONE_REGEX = /^\+[1-9][0-9]\d{1,14}$/; diff --git a/apps/frontend/src/hooks/ramp/useRampNavigation.ts b/apps/frontend/src/hooks/ramp/useRampNavigation.ts index e067c5706..863647462 100644 --- a/apps/frontend/src/hooks/ramp/useRampNavigation.ts +++ b/apps/frontend/src/hooks/ramp/useRampNavigation.ts @@ -25,10 +25,6 @@ export const useRampNavigation = ( const shouldSkipQuoteForm = useMemo(() => searchParams.quoteId || hasAllQuoteRefreshParams(searchParams), [searchParams]); const getCurrentComponent = useCallback(() => { - if (shouldSkipQuoteForm) { - return formComponent; - } - if (rampState?.ramp?.currentPhase === "complete") { return successComponent; } @@ -41,19 +37,23 @@ export const useRampNavigation = ( return progressComponent; } + if (shouldSkipQuoteForm) { + return formComponent; + } + if (rampMachineState.value === "Idle") { return quoteComponent; } return formComponent; }, [ - shouldSkipQuoteForm, rampState, - formComponent, + rampMachineState.value, + shouldSkipQuoteForm, successComponent, failureComponent, progressComponent, - rampMachineState.value, + formComponent, quoteComponent ]); diff --git a/apps/frontend/src/hooks/ramp/useRampValidation.ts b/apps/frontend/src/hooks/ramp/useRampValidation.ts index 82c002043..49e380969 100644 --- a/apps/frontend/src/hooks/ramp/useRampValidation.ts +++ b/apps/frontend/src/hooks/ramp/useRampValidation.ts @@ -4,6 +4,7 @@ import { getAnyFiatTokenDetails, getOnChainTokenDetailsOrDefault, OnChainTokenDetails, + QuoteError, QuoteResponse, RampDirection } from "@vortexfi/shared"; @@ -17,7 +18,7 @@ import { TrackableEvent, useEventsContext } from "../../contexts/events"; import { useNetwork } from "../../contexts/network"; import { multiplyByPowerOfTen, stringifyBigWithSignificantDecimals } from "../../helpers/contracts"; import { useQuoteFormStore } from "../../stores/quote/useQuoteFormStore"; -import { useQuote, useQuoteError } from "../../stores/quote/useQuoteStore"; +import { useQuote, useQuoteError, useQuoteLoading } from "../../stores/quote/useQuoteStore"; import { useRampDirection } from "../../stores/rampDirectionStore"; import { useOnchainTokenBalance } from "../useOnchainTokenBalance"; import { useVortexAccount } from "../useVortexAccount"; @@ -35,30 +36,20 @@ function validateOnramp( } ): string | null { const maxAmountUnits = multiplyByPowerOfTen(Big(fromToken.maxBuyAmountRaw), -fromToken.decimals); - // Set minimum amount for EURC to 1 unit as an arbitrary limit. - const minAmountUnits = - fromToken.assetSymbol === "EURC" ? new Big(1) : multiplyByPowerOfTen(Big(fromToken.minBuyAmountRaw), -fromToken.decimals); + const minAmountUnits = multiplyByPowerOfTen(Big(fromToken.minBuyAmountRaw), -fromToken.decimals); - if (inputAmount && maxAmountUnits.lt(inputAmount)) { - trackEvent({ - error_message: "more_than_maximum_withdrawal", - event: "form_error", - input_amount: inputAmount ? inputAmount.toString() : "0" - }); - return t("pages.swap.error.moreThanMaximumWithdrawal.buy", { - assetSymbol: fromToken.fiat.symbol, - maxAmountUnits: stringifyBigWithSignificantDecimals(maxAmountUnits, 2) - }); - } + const isTooHigh = inputAmount && maxAmountUnits.lt(inputAmount); + const isTooLow = inputAmount && !inputAmount.eq(0) && minAmountUnits.gt(inputAmount); - if (inputAmount && !inputAmount.eq(0) && minAmountUnits.gt(inputAmount)) { + if (isTooHigh || isTooLow) { trackEvent({ - error_message: "less_than_minimum_withdrawal", + error_message: isTooHigh ? "more_than_maximum_withdrawal" : "less_than_minimum_withdrawal", event: "form_error", input_amount: inputAmount ? inputAmount.toString() : "0" }); - return t("pages.swap.error.lessThanMinimumWithdrawal.buy", { + return t("pages.swap.error.amountOutOfRange.buy", { assetSymbol: fromToken.fiat.symbol, + maxAmountUnits: stringifyBigWithSignificantDecimals(maxAmountUnits, 2), minAmountUnits: stringifyBigWithSignificantDecimals(minAmountUnits, 2) }); } @@ -74,6 +65,7 @@ function validateOfframp( toToken, quote, userInputTokenBalance, + isDisconnected, trackEvent }: { inputAmount: Big; @@ -81,56 +73,45 @@ function validateOfframp( toToken: FiatTokenDetails; quote: QuoteResponse; userInputTokenBalance: string | null; + isDisconnected: boolean; trackEvent: (event: TrackableEvent) => void; } ): string | null { - if (typeof userInputTokenBalance === "string") { - const isNativeToken = fromToken.isNative; - if (Big(userInputTokenBalance).lt(inputAmount ?? 0)) { - trackEvent({ - error_message: "insufficient_balance", - event: "form_error", - input_amount: inputAmount ? inputAmount.toString() : "0" - }); - return t("pages.swap.error.insufficientFunds", { - assetSymbol: fromToken?.assetSymbol, - userInputTokenBalance - }); - // If the user chose the max amount, show a warning for native tokens due to gas fees - } else if (isNativeToken && Big(userInputTokenBalance).eq(inputAmount)) { - return t("pages.swap.error.gasWarning"); - } - } - const maxAmountUnits = multiplyByPowerOfTen(Big(toToken.maxSellAmountRaw), -toToken.decimals); const minAmountUnits = multiplyByPowerOfTen(Big(toToken.minSellAmountRaw), -toToken.decimals); + const amountOut = quote ? Big(quote.outputAmount) : Big(0); + + const isTooHigh = inputAmount && quote && maxAmountUnits.lt(amountOut); + const isTooLow = !amountOut.eq(0) && !config.test.overwriteMinimumTransferAmount && minAmountUnits.gt(amountOut); - if (inputAmount && quote && maxAmountUnits.lt(Big(quote.outputAmount))) { + if (isTooHigh || isTooLow) { trackEvent({ - error_message: "more_than_maximum_withdrawal", + error_message: isTooHigh ? "more_than_maximum_withdrawal" : "less_than_minimum_withdrawal", event: "form_error", input_amount: inputAmount ? inputAmount.toString() : "0" }); - return t("pages.swap.error.moreThanMaximumWithdrawal.sell", { + return t("pages.swap.error.amountOutOfRange.sell", { assetSymbol: toToken.fiat.symbol, - maxAmountUnits: stringifyBigWithSignificantDecimals(maxAmountUnits, 2) + maxAmountUnits: stringifyBigWithSignificantDecimals(maxAmountUnits, 2), + minAmountUnits: stringifyBigWithSignificantDecimals(minAmountUnits, 2) }); } - const amountOut = quote ? Big(quote.outputAmount) : Big(0); - - if (!amountOut.eq(0)) { - if (!config.test.overwriteMinimumTransferAmount && minAmountUnits.gt(amountOut)) { + if (typeof userInputTokenBalance === "string" && !isDisconnected) { + const isNativeToken = fromToken.isNative; + if (Big(userInputTokenBalance).lt(inputAmount ?? 0)) { trackEvent({ - error_message: "less_than_minimum_withdrawal", + error_message: "insufficient_balance", event: "form_error", input_amount: inputAmount ? inputAmount.toString() : "0" }); - - return t("pages.swap.error.lessThanMinimumWithdrawal.sell", { - assetSymbol: toToken.fiat.symbol, - minAmountUnits: stringifyBigWithSignificantDecimals(minAmountUnits, 2) + return t("pages.swap.error.insufficientFunds", { + assetSymbol: fromToken?.assetSymbol, + userInputTokenBalance }); + // If the user chose the max amount, show a warning for native tokens due to gas fees + } else if (isNativeToken && Big(userInputTokenBalance).eq(inputAmount)) { + return t("pages.swap.error.gasWarning"); } } @@ -158,6 +139,7 @@ export const useRampValidation = () => { const { inputAmount: inputAmountString, onChainToken, fiatToken } = useQuoteFormStore(); const quote = useQuote(); + const quoteLoading = useQuoteLoading(); const quoteError = useQuoteError(); const { selectedNetwork } = useNetwork(); const { trackEvent } = useEventsContext(); @@ -178,16 +160,36 @@ export const useRampValidation = () => { }); const getCurrentErrorMessage = useCallback(() => { - if (quoteError) return t(quoteError); - - if (isDisconnected) return; + if (quoteLoading) return null; // First check if the fiat token is enabled const tokenAvailabilityError = validateTokenAvailability(t, fiatToken, trackEvent); if (tokenAvailabilityError) return tokenAvailabilityError; - let validationError = null; + // For offramps, we must also show a valid error message, when backend refuses to calculate a quote + // due to limits. + + const fiatTokenDetails = getAnyFiatTokenDetails(fiatToken); + if (quoteError?.includes(QuoteError.BelowLowerLimitSell)) { + const maxAmountUnits = multiplyByPowerOfTen(Big(fiatTokenDetails.maxSellAmountRaw), -toToken.decimals); + const minAmountUnits = multiplyByPowerOfTen(Big(fiatTokenDetails.minSellAmountRaw), -toToken.decimals); + return t("pages.swap.error.amountOutOfRange.sell", { + assetSymbol: toToken.assetSymbol, + maxAmountUnits: stringifyBigWithSignificantDecimals(maxAmountUnits, 2), + minAmountUnits: stringifyBigWithSignificantDecimals(minAmountUnits, 2) + }); + } else if (quoteError?.includes(QuoteError.BelowLowerLimitBuy) || quoteError?.includes(QuoteError.InputAmountTooLow)) { + const maxAmountUnits = multiplyByPowerOfTen(Big(fiatTokenDetails.maxBuyAmountRaw), -fromToken.decimals); + const minAmountUnits = multiplyByPowerOfTen(Big(fiatTokenDetails.minBuyAmountRaw), -fromToken.decimals); + return t("pages.swap.error.amountOutOfRange.buy", { + assetSymbol: fromToken.assetSymbol, + maxAmountUnits: stringifyBigWithSignificantDecimals(maxAmountUnits, 2), + minAmountUnits: stringifyBigWithSignificantDecimals(minAmountUnits, 2) + }); + } else if (quoteError) return t(quoteError); + + let validationError = null; if (isOnramp) { validationError = validateOnramp(t, { fromToken: fromToken as FiatTokenDetails, @@ -198,6 +200,7 @@ export const useRampValidation = () => { validationError = validateOfframp(t, { fromToken: fromToken as OnChainTokenDetails, inputAmount, + isDisconnected, quote: quote as QuoteResponse, toToken: toToken as FiatTokenDetails, trackEvent, @@ -218,6 +221,7 @@ export const useRampValidation = () => { trackEvent, toToken, quote, + quoteLoading, userInputTokenBalance?.balance, fiatToken ]); diff --git a/apps/frontend/src/pages/progress/index.tsx b/apps/frontend/src/pages/progress/index.tsx index 63926030b..bcf0c77c0 100644 --- a/apps/frontend/src/pages/progress/index.tsx +++ b/apps/frontend/src/pages/progress/index.tsx @@ -377,10 +377,10 @@ const ProgressContent: FC = ({ ); return ( - +
{showIsDelayedWarning && } -

{t("pages.progress.closeProgressScreenText")}

+

{t("pages.progress.closeProgressScreenText")}

+

VORTEX FINANCE - TERMS OF USE

+

Last updated: 13th Jan 2025

+ +
+

1. Introduction, Parties and Acceptance

+

+ 1.1 These Terms of Use ("Terms") govern your access to and use of: +

+
    +
  • + the website operated at{" "} + + https://www.vortexfinance.co/ + {" "} + and any related sub-domains (the "Website"); +
  • +
  • + any Vortex finance widget, iframe or similar interface embedded in third-party websites or applications (the " + Widget"); +
  • +
  • + any publicly exposed application programming interfaces, software development kits or other technical interfaces + made available by Vortex (collectively, the "APIs"); and +
  • +
  • + all related services provided by or through Vortex (together, the "Services"). +
  • +
+

+ 1.2 The Services are provided by SatoshiPay Ltd., a company incorporated in{" "} + England and Wales with its registered office at{" "} + Hill Dickinson Llp, The Broadgate Tower, 20 Primrose Street, London, EC2A 2EW United Kingdom (" + Vortex", "we", "us", "our"). +

+

+ 1.3 These Terms form a legally binding agreement between you, as an individual end-user of the + Services ("you", "your", "User" or "End User"), + and Vortex. +

+

+ 1.4 By accessing or using the Website, Widget or APIs, or by initiating or attempting to initiate any + transaction through the Services, you: +

+
    +
  • acknowledge that you have read and understood these Terms;
  • +
  • agree to be bound by these Terms; and
  • +
  • + agree that these Terms apply regardless of whether you access Vortex directly or via a third-party site or + application integrating the Widget or APIs. +
  • +
+

+ 1.5 If you do not agree with these Terms, you must not access or + use the Website, the Widget, the APIs or any other part of the Services. +

+

+ 1.6 These Terms incorporate by reference and should be read together with: +

+
    +
  • + our Privacy Policy, available at https://www.vortexfinance.co/en/privacy-policy; +
  • +
  • + our Cookie Policy, available at https://www.vortexfinance.co/en/privacy-policy; and +
  • +
  • + any Partner-specific terms, payment provider terms, wallet provider terms or other third-party terms presented to + you in the course of using the Services (collectively, "Third-Party Terms"). +
  • +
+

+ In the event of any conflict between these Terms and Third-Party Terms in relation to the fiat-crypto transaction + itself, the relevant Third-Party Terms of the Partner, bank, card issuer or payment service provider will generally + prevail for that transaction, except where mandatory law requires otherwise. +

+
+ +
+

2. Definitions

+

+ 2.1 In these Terms, unless the context requires otherwise: +

+
    +
  • + "API(s)" has the meaning given in clause 1.1 and includes any associated documentation, keys, SDKs + and sample code. +
  • +
  • + "Business Day" means a day other than a Saturday, Sunday or public holiday. +
  • +
  • + "Designated Wallet" means a Digital Asset wallet or address specified by you for receipt or sending + of Digital Assets in connection with a Transaction. +
  • +
  • + "Digital Asset" means any cryptographic token, virtual currency, crypto-asset, stablecoin, or + similar digital representation of value that is supported by the Services from time to time, excluding Fiat + Currency. +
  • +
  • + "Fiat Currency" means legal tender issued by a central bank or government, such as USD, EUR, BRL or + any other fiat currency supported from time to time. +
  • +
  • + "Order" means a request by you, via the Services, to buy or sell a Digital Asset or Fiat Currency + at a given Quote or within specified parameters. +
  • +
  • + "Partner" means any independent third-party service provider (including but not limited to + regulated virtual asset service providers, payment institutions, e-money institutions, banks, card acquirers, PSPs + and liquidity providers) that provides fiat-crypto conversion, payment processing, payout, settlement or related + services in connection with Transactions directly to the user with its own regulation on its own terms. +
  • +
  • + "Quote" means a price indication or exchange rate, including associated fees, limits and other + parameters, shown to you via the Services for a potential Transaction, which may be time-limited and conditional. +
  • +
  • + "Sanctions" means any economic, financial or trade sanctions laws, regulations, embargoes, + restrictive measures or similar regimes administered or enforced by the United Nations, the European Union, any EU + Member State, the United Kingdom, the United States (including OFAC), or any other relevant governmental or + regulatory authority. +
  • +
  • + "Services" has the meaning given in clause 1.1. +
  • +
  • + "Third-Party Services" means services provided by Partners, banks, card schemes, payment service + providers, wallet providers, identity verification providers, analytics providers, hosting providers and any other + third parties engaged in connection with the Services. +
  • +
  • + "Transaction" means any fiat-crypto or crypto-fiat conversion, and any associated payment, payout + or transfer, that is initiated or facilitated through the Services. +
  • +
  • + "Wallet" means any software, hardware or custodial wallet that can store or manage Digital Assets. +
  • +
  • + "Website", "Widget" and "APIs" have the meanings given in clause + 1.1. +
  • +
+

+ 2.2 Other capitalised terms are defined in the body of these Terms. References to the singular + include the plural and vice versa. +

+
+ +
+

3. Scope and Nature of the Services

+

+ 3.1 Vortex provides a technical aggregation and routing layer that displays Quotes from Partners for + fiat-crypto and crypto-fiat Transactions and enables the technical initiation, routing and status tracking of such + Transactions. +

+

+ 3.2 Vortex is not a bank, payment institution, e-money institution, exchange, + investment firm, broker, custodian, or wallet provider, and does not provide legal, tax or investment advice. +

+

+ 3.3 The fiat-crypto conversion and any related payment or payout are executed by a Partner, through a + decentralized exchange, but not by Vortex. The Partner acts as merchant of record and is responsible for KYC/AML, + transaction monitoring and regulatory compliance for its leg of the Transaction. +

+

+ 3.4 Vortex does not: +

+
    +
  • hold or control your Fiat Currency or Digital Assets at any time;
  • +
  • operate or control your Wallet or private keys;
  • +
  • provide ongoing custody or account balances; or
  • +
  • guarantee execution of any Order or performance of any Partner.
  • +
+

+ 3.5 Vortex may modify, suspend or discontinue parts of the Services, supported assets, payment + methods or Partners at any time, where required for legal, regulatory, security or operational reasons. +

+

+ 3.6 Certain features or jurisdictions may be offered only in cooperation with one or more specific + Partners and may be subject to additional terms, eligibility criteria or regulatory requirements. +

+
+ +
+

4. Eligibility, Territories and Restricted Use

+

+ 4.1 You may use the Services only if, at the time of each use: +

+
    +
  • + you are at least 18 years old or have reached the age of majority in your jurisdiction, whichever + is higher; and +
  • +
  • you have the legal capacity to enter into binding contracts.
  • +
+

+ 4.2 You must not use the Services if: +

+
    +
  • + use of the Services, or the acquisition, holding or disposal of the relevant Digital Assets, is prohibited, + restricted or requires licences or registrations under the laws applicable to you; +
  • +
  • + you are a resident, citizen or located in any country, territory or jurisdiction that is subject to comprehensive + Sanctions or which Vortex or any Partner designates as high-risk or restricted from time to time (collectively, " + Restricted Jurisdictions"); or +
  • +
  • + you or any beneficial owner of your funds is: +
      +
    • listed on, or owned or controlled by a person listed on, any applicable Sanctions list; or
    • +
    • otherwise the subject of Sanctions.
    • +
    +
  • +
+

+ 4.3 Vortex and the Partners reserve the right, at any time and in their sole discretion, to: +

+
    +
  • refuse to provide the Services in relation to any jurisdiction, person or use case;
  • +
  • block, restrict or terminate your access to some or all of the Services; or
  • +
  • cancel or suspend Transactions,
  • +
+

+ where they reasonably believe that such action is necessary or desirable for legal, regulatory, sanctions, fraud, AML, + risk management, reputational or similar reasons. +

+

+ 4.4 You are solely responsible for determining whether your use of the Services is lawful in the + jurisdiction(s) where you are resident, located or otherwise subject to law, and for complying with all applicable + laws and regulations. +

+
+ +
+

5. Account, Access and KYC

+

+ 5.1 Vortex may allow or require you to create an account or profile to access certain features of the + Services. If so, you must: +

+
    +
  • provide accurate, current and complete information;
  • +
  • keep that information up-to-date; and
  • +
  • maintain the confidentiality and security of any login credentials or API keys.
  • +
+

+ 5.2 You are responsible for all activities that occur under your account or using your API keys, + whether or not authorised by you. You must notify us promptly at support@vortexfinance.co if you + suspect any unauthorised access or compromise. +

+

+ 5.3 Partners and/or their designated service providers may require you to undergo{" "} + KYC/AML checks, including the provision of: +

+
    +
  • identity documents;
  • +
  • proof of address;
  • +
  • information about source of funds or source of wealth; and
  • +
  • any other information reasonably required for compliance purposes.
  • +
+

+ 5.4 KYC/AML checks may be performed: +

+
    +
  • directly by the Partner;
  • +
  • by Vortex on behalf of the Partner; and/or
  • +
  • by third-party identity verification or compliance service providers.
  • +
+

+ 5.5 If you fail to provide requested information, or if KYC/AML checks cannot be completed to the + satisfaction of the Partner or Vortex: +

+
    +
  • your Orders or Transactions may be refused, cancelled or delayed;
  • +
  • your access to some or all of the Services may be suspended or terminated; and
  • +
  • any funds may be handled in accordance with applicable law and the relevant Partner's policies.
  • +
+
+ +
+

6. Quotes, Orders, Execution and Limits

+

+ 6.1 A Quote shown via the Services is indicative or firm as stated in the interface and is subject to + time limits and availability. +

+

+ 6.2 A Quote will generally become binding for the relevant Partner only when all of + the following have occurred (unless the interface clearly states otherwise): +

+
    +
  • you have confirmed the Order and explicitly consented to proceed;
  • +
  • + the Partner or payment provider has confirmed receipt and successful authorisation of the required Fiat Currency or + Digital Asset amount; and +
  • +
  • any required KYC/AML or other compliance checks have been completed without unresolved issues.
  • +
+

+ 6.3 Vortex does not commit to, and does not guarantee, any particular execution time, price or + outcome for Orders. Execution is conditional upon: +

+
    +
  • availability of liquidity;
  • +
  • successful receipt and settlement of funds by the relevant Partner;
  • +
  • successful completion of required compliance checks; and
  • +
  • absence of technical failures, security incidents, network congestion or external constraints.
  • +
+

+ 6.4 Vortex and/or its Partners may set and change transaction limits at any time, including minimum + and maximum amounts and periodic volume limits per User, Partner, asset, network or jurisdiction. +

+

+ 6.5 Vortex and/or any Partner may refuse, cancel or reverse an Order or Transaction (in whole or in + part), or apply additional checks, where permitted by law, including if: +

+
    +
  • the Order appears unusual, high-risk, fraudulent or linked to prohibited activities;
  • +
  • the Quote has expired or is manifestly erroneous;
  • +
  • payment is not received in full, on time or from an acceptable source; or
  • +
  • technical, legal, regulatory, sanctions or compliance reasons justify such action.
  • +
+

+ 6.6 Where an Order is cancelled or not executed after you have sent funds, the refund or return of + such funds (if any) will be handled by the relevant Partner, bank or payment provider according to their policies and + applicable law. Vortex is not responsible for any delays, FX movements or fees associated with such processes. +

+
+ +
+

7. Fees, Pricing, FX and Promotions

+

+ 7.1 Various fees may apply to the use of the Services, including: +

+
    +
  • Vortex service fees;
  • +
  • Partner fees;
  • +
  • card processing or banking fees;
  • +
  • + blockchain or network fees; and +
  • +
  • spreads or margins embedded in the exchange rate of partners and decentralised exchanges.
  • +
+

+ 7.2 To the extent practicable, applicable fees and charges will be disclosed to you in the flow + before you confirm an Order. However, certain third-party fees (such as bank charges or unexpected network fees) may + not be fully visible in advance. +

+

+ 7.3 Exchange rates and prices for Digital Assets and Fiat Currencies are{" "} + highly volatile and may change rapidly. The rates shown in a Quote: +

+
    +
  • may differ from rates available on other platforms, at other times, or via other providers; and
  • +
  • may be updated frequently, even within short time frames.
  • +
+

+ 7.4 Vortex or a Partner may from time to time run promotions, subsidies, discounts + or "zero-fee" campaigns. Unless expressly stated otherwise: +

+
    +
  • + such campaigns are time-limited and subject to change or withdrawal at any time; +
  • +
  • + eligibility may depend on criteria such as asset, jurisdiction, payment method, volume or specific integrator; and +
  • +
  • caps or quotas may apply per User, per Partner, per campaign or overall.
  • +
+

+ 7.5 You acknowledge that rounding differences and small residual amounts can occur due to: +

+
    +
  • the decimal precision of certain Digital Assets or Fiat Currencies;
  • +
  • FX conversions; and
  • +
  • technical limitations.
  • +
+

+ Subject to mandatory law, such residual amounts may be retained by the Partner or by Vortex where operationally + impractical to allocate otherwise. +

+
+ +
+

8. Risk Disclosure

+

+ 8.1 Digital Assets and related activities involve significant risks, including the + risk of loss of some or all of the value of your holdings. Before using the Services, you should carefully consider + whether you can afford to lose any amounts involved. +

+

+ 8.2 Without limitation, you acknowledge the following risks: +

+

+ (a) Market and volatility risk +

+

+ Digital Asset and FX markets are highly volatile. Prices can move rapidly and unexpectedly, and Digital Assets may + lose all or most of their value. +

+

+ (b) Technology and network risk +

+

+ Digital Assets rely on blockchain and other distributed ledger technologies. There may be bugs, vulnerabilities, + forks, network congestion, delays, failed transactions, double-spends or other technical issues. +

+

+ (c) Irreversibility and user error +

+

+ Transactions on many blockchains are generally irreversible once confirmed. If you provide an + incorrect address, select the wrong network, send to an unsupported asset or contract, or otherwise make an error, you + may permanently lose the funds involved. +

+

+ (d) Regulatory and legal risk +

+

+ Laws and regulations relating to Digital Assets and fiat-crypto conversions are evolving and may change quickly. New + rules may: +

+
    +
  • restrict, prohibit or impose new obligations on the Services or specific assets; or
  • +
  • require reporting, disclosure or other compliance actions.
  • +
+

+ (e) Counterparty and credit risk +

+

+ Banks, card issuers, PSPs, Partners and other intermediaries may experience operational issues, financial + difficulties, regulatory restrictions or insolvency. Their failure could result in delays, losses or inability to + complete Transactions. +

+

+ (f) Security risk +

+

+ Your devices, Wallets, private keys, seed phrases and accounts may be targeted by malware, phishing, social + engineering, SIM-swap attacks or other forms of cybercrime. Compromise of your credentials may lead to loss of funds. +

+

+ 8.3 Vortex does not provide: +

+
    +
  • investment, financial or trading advice;
  • +
  • legal, accounting or tax advice; or
  • +
  • recommendations on whether to buy, sell or hold any Digital Asset.
  • +
+

+ Any information provided through the Services is of a general nature only and does not take into account your personal + circumstances. You are solely responsible for your decisions. +

+

+ 8.4 You should seek independent professional advice before entering into any Transaction and before + relying on any information obtained through the Services. +

+
+ +
+

9. Wallets and Addresses

+

+ 9.1 You must provide accurate and compatible Designated Wallet addresses and select + the correct blockchain network and asset when using the Services. +

+

+ 9.2 Vortex does not control, operate or maintain your Wallet(s). We do not store or have access to + your private keys, seed phrases or backup phrases. We cannot recover or restore your Wallet if you lose access. +

+

+ 9.3 Vortex cannot and will not: +

+
    +
  • + recover Digital Assets sent to the wrong address, wrong network, unsupported asset, or smart contract not under the + control of Vortex or a Partner; or +
  • +
  • reverse or modify blockchain transactions once broadcast.
  • +
+

+ 9.4 You are solely responsible for: +

+
    +
  • verifying the accuracy of all wallet addresses and network selections;
  • +
  • ensuring that your Wallet supports the Digital Asset and network involved; and
  • +
  • + maintaining appropriate security for your Wallet and devices including the backup of recovery keys (seed phrases) of + the wallets used. +
  • +
+
+ +
+

10. Third-Party Services and Partners

+

+ 10.1 The Services rely on Third-Party Services, including Partners, PSPs, banks, + card schemes, wallet providers, identity verification providers, data centres and infrastructure vendors. Vortex does + not control and is not responsible for: +

+
    +
  • the content, terms, privacy practices, availability or security of Third-Party Services; or
  • +
  • any action or omission of any third party.
  • +
+

+ 10.2 Your use of Third-Party Services may be subject to Third-Party Terms. You are + responsible for reviewing and complying with those terms. If you do not accept those terms, you should not use the + related part of the Services. +

+

+ 10.3 Any disputes, chargebacks, reversals, refunds or claims relating to: +

+
    +
  • bank transfers;
  • +
  • card payments;
  • +
  • payment-initiated debits; or
  • +
  • payout failures
  • +
+

+ are primarily between you and the relevant bank, card issuer, PSP or Partner, subject to applicable law. Vortex may + assist at its discretion but is under no obligation to become involved in such disputes. +

+

+ 10.4 Vortex may receive fees, revenue shares or other economic benefits from Partners or other third + parties in relation to your use of the Services. This does not alter your obligations or our disclaimers under these + Terms. +

+
+ +
+

11. Refunds, Chargebacks and Reversals

+

+ 11.1 Fiat refunds (if any) for Transactions are generally handled by the relevant + Partner, bank or PSP according to their policies and applicable law. Vortex does not hold your fiat funds and is not + responsible for: +

+
    +
  • the timing of any refund;
  • +
  • FX rate changes that occur before a refund is processed; or
  • +
  • any intermediary bank or card fees.
  • +
+

+ 11.2 On-chain Digital Asset transfers are normally non-reversible. + Once a Transaction has been submitted to the blockchain, it cannot be undone by Vortex. +

+

+ 11.3 If you initiate a chargeback, dispute or reversal with your bank or card + issuer: +

+
    +
  • Vortex and/or the relevant Partner may suspend or terminate your access to the Services;
  • +
  • investigations may be carried out and additional documentation requested from you; and
  • +
  • + where a chargeback is deemed illegitimate or abusive, Vortex or the Partner may seek to recover resulting losses, + including through collection efforts or legal action, where permitted by law. +
  • +
+

+ 11.4 In case of over-payments, under-payments, late payments, + expired Quotes, or partial fills: +

+
    +
  • + the Partner may, at its discretion and subject to law, complete the Transaction at a different rate, partially + fulfil it, or refund the relevant amount; and +
  • +
  • Vortex is not liable for any resulting FX differences, network fees or opportunity costs.
  • +
+
+ +
+

12. Taxes

+

+ 12.1 You are solely responsible for: +

+
    +
  • + determining which taxes (if any) apply to your Transactions and holdings (including income, capital gains, VAT, GST, + stamp duty or similar); +
  • +
  • reporting such taxes to the relevant authorities; and
  • +
  • paying any such taxes in accordance with applicable law.
  • +
+

+ 12.2 Vortex does not provide tax advice and does not assume any responsibility for your tax + obligations. +

+

+ 12.3 Vortex, Partners or other third parties may be required by law to: +

+
    +
  • collect, withhold or remit certain taxes or levies; or
  • +
  • report information about you, your Transactions or your accounts to tax or other authorities.
  • +
+

You consent to such actions to the extent required by applicable law.

+
+ +
+

13. User Obligations and Prohibited Activities

+

+ 13.1 You agree to use the Services only for lawful purposes and in accordance with + these Terms and all applicable laws. +

+

+ 13.2 You must not use the Services, directly or indirectly, for any of the following + ("Prohibited Activities"): +

+
    +
  • any activity that would violate or attempt to circumvent Sanctions, AML, CTF or other financial crime laws;
  • +
  • fraud, scams, misrepresentation, identity theft, "phishing", or similar deceptive practices;
  • +
  • financing or facilitating terrorism, proliferation of weapons, or other serious crime;
  • +
  • dealing in the proceeds of crime;
  • +
  • ransomware payments, extortion, blackmail or similar schemes;
  • +
  • + operation of or interaction with darknet markets, mixing services/tumblers (except where expressly permitted by law + and policy), or privacy-enhancing technologies where restricted; +
  • +
  • unlicensed money transmission, payment services, banking or investment services;
  • +
  • unauthorised or illegal gambling, betting or gaming, where restricted by law or by Vortex policy;
  • +
  • + dealing in goods or services that are illegal in your jurisdiction or the jurisdiction of Vortex, a Partner or any + relevant payment provider; +
  • +
  • infringement of any third party's intellectual property or privacy rights;
  • +
  • + attempting to access or probe systems without authorisation, or interfering with the security or integrity of any + network or system; +
  • +
  • + using bots, scrapers or automated tools in a manner that burdens or disrupts the Services, except as permitted by + our API terms; +
  • +
  • + reverse engineering, decompiling or attempting to derive source code from any part of the Services, except to the + extent permitted by mandatory law; or +
  • +
  • any other activity that Vortex reasonably considers to be high-risk, abusive or harmful.
  • +
+

+ 13.3 Vortex may, at its discretion, monitor use of the Services (in compliance with applicable law) + and may report any suspicious activity to relevant authorities and Partners. +

+
+ +
+

14. Intellectual Property and Licence

+

+ 14.1 All intellectual property rights in and to the Services, including but not limited to: +

+
    +
  • the Website, Widget and APIs;
  • +
  • software, source code and object code;
  • +
  • documentation, text, graphics, logos, trademarks, trade names and interface designs;
  • +
  • databases and data models; and
  • +
  • any improvements, modifications or derivative works,
  • +
+

+ are owned by Vortex or its licensors. All rights not expressly granted under these Terms are reserved. +

+

+ 14.2 Subject to your compliance with these Terms, Vortex grants you a{" "} + limited, revocable, non-exclusive, non-transferable, non-sublicensable licence to: +

+
    +
  • access and use the Website and Widget for your personal, lawful purposes; and
  • +
  • + where applicable, use publicly documented endpoints of the APIs for lawful purposes, within any rate limits or usage + policies communicated by Vortex. +
  • +
+

+ 14.3 If you integrate or embed the Widget or call the APIs from your own site or application without + a separate written agreement with Vortex, you additionally agree that: +

+
    +
  • you will not misrepresent the nature of the Services or your relationship with Vortex;
  • +
  • you will comply with all technical and branding guidelines that Vortex may publish from time to time;
  • +
  • + you will not modify, obscure or remove any Vortex branding, legal notices or links included in the Widget without + our prior written consent; and +
  • +
  • Vortex may suspend or revoke your integration or API access at any time.
  • +
+

+ 14.4 Nothing in these Terms grants you any rights to use Vortex's names, logos or trademarks except + as strictly necessary to use the Services in accordance with these Terms or as permitted in a separate agreement. +

+
+ +
+

15. Service Availability, Changes and Maintenance

+

+ 15.1 The Services are provided on an "as is" and "as available"{" "} + basis. Vortex does not guarantee: +

+
    +
  • continuous, uninterrupted or error-free access to the Services; or
  • +
  • that any defect or error will be corrected.
  • +
+

+ 15.2 Vortex may temporarily suspend or limit all or part of the Services for: +

+
    +
  • scheduled or emergency maintenance;
  • +
  • security incidents or vulnerabilities;
  • +
  • Partner or third-party outages;
  • +
  • compliance with legal or regulatory requirements; or
  • +
  • other reasonable operational reasons.
  • +
+

+ 15.3 Vortex may change or discontinue features, supported assets, networks, payment methods or + Partners at any time. Where practicable, we will aim to provide notice of material changes, but we are not obligated + to do so in all circumstances. +

+
+ +
+

16. Data Protection and Privacy

+

+ 16.1 Vortex processes personal data in accordance with its Privacy Policy at + https://www.vortexfinance.co/en/privacy-policy which forms part of these Terms. By using the Services, you acknowledge + and agree to the processing of your personal data as described there. +

+

+ 16.2 Without limiting the Privacy Policy, you acknowledge that: +

+
    +
  • + Vortex collects and processes personal data such as identification data, contact details, transaction data, device + information and usage data; +
  • +
  • + certain data may be shared with Partners, PSPs, banks, analytics providers, identity verification providers and + other Third-Party Services for purposes including: +
      +
    • KYC/AML and fraud prevention;
    • +
    • transaction processing and settlement;
    • +
    • compliance with legal obligations; and
    • +
    • service improvement and analytics; and
    • +
    +
  • +
  • + data may be processed in and transferred to multiple jurisdictions, which may have different data protection laws, + subject to appropriate safeguards. +
  • +
+

+ 16.3 Where required by law, we will seek your consent for specific types of processing (for example, + cookies or marketing communications). You may withdraw such consent at any time in accordance with the Privacy Policy, + but this may limit your ability to use parts of the Services. +

+
+ +
+

17. Disclaimers and Limitation of Liability

+

+ 17.1 Nothing in these Terms excludes or limits any liability that cannot be excluded or limited under + applicable law, including liability for fraud or fraudulent misrepresentation. +

+

+ 17.2 To the maximum extent permitted by applicable law, and subject to clause 17.1: +

+

+ (a) No warranties +

+

+ The Services are provided on an "as is" and "as available" basis. Vortex makes no representations or warranties of any + kind, express or implied, including: +

+
    +
  • + any implied warranties of merchantability, fitness for a particular purpose, non-infringement, or satisfactory + quality; or +
  • +
  • any warranty that the Services will be uninterrupted, secure, or error-free.
  • +
+

+ (b) No responsibility for market movements +

+

+ Vortex is not responsible for any loss resulting from market movements, volatility, slippage, or price differences + between Quotes and other venues. +

+

+ (c) Third-party and network failures +

+

Vortex is not liable for any loss or damage arising from:

+
    +
  • actions or omissions of Partners, banks, card schemes, PSPs, wallet providers or other third parties;
  • +
  • blockchain or network failures, forks, attacks, congestion, bugs or other technical events; or
  • +
  • + force majeure events, including natural disasters, war, terrorism, strikes, internet or telecom failures, or + governmental actions beyond our reasonable control. +
  • +
+

+ 17.3 To the maximum extent permitted by law, Vortex will not be liable for: +

+
    +
  • any loss of profits, revenue, business, contracts, anticipated savings, goodwill or opportunity;
  • +
  • any indirect, incidental, special, punitive or consequential loss or damage; or
  • +
  • any loss or corruption of data.
  • +
+

+ 17.4 Subject to clauses 17.1-17.3, and to the extent permitted by law, Vortex's aggregate liability + to you arising out of or in connection with the Services and these Terms, whether in contract, tort (including + negligence), breach of statutory duty or otherwise, will be limited to the lesser of: +

+
    +
  • + the total amount of net fees actually paid by you to Vortex in connection with your use of the + Services in the 12-month period immediately preceding the event giving rise to the claim; and +
  • +
  • + 5000 USD +
  • +
+
+ +
+

18. Indemnity

+

+ 18.1 To the maximum extent permitted by law, you agree to indemnify and hold + harmless Vortex, its affiliates, directors, officers, employees and agents from and against any and all claims, + demands, losses, damages, costs and expenses (including reasonable legal fees) arising out of or in connection with: +

+
    +
  • your breach of these Terms or of any applicable law or regulation;
  • +
  • your use or misuse of the Services;
  • +
  • any Prohibited Activities carried out by you or through your account;
  • +
  • any claim by a third party relating to your use of the Services, your Wallet, or your Transactions; or
  • +
  • + any chargeback, dispute, reversal or recovery action initiated by you where such action is found to be abusive, + fraudulent or otherwise unjustified. +
  • +
+

+ 18.2 Vortex may, at its own expense, assume control of the defence of any matter subject to + indemnification by you. You must cooperate fully with Vortex in such defence. +

+
+ +
+

19. Suspension, Termination and Closing

+

+ 19.1 Vortex may, at any time and with or without prior notice (to the extent permitted by law), + suspend or terminate your access to all or part of the Services, or close or restrict your account, if: +

+
    +
  • you breach these Terms or any applicable law;
  • +
  • + Vortex or a Partner reasonably suspects fraud, money laundering, terrorist financing or other financial crime; +
  • +
  • required by law, regulation, court order, law enforcement or a regulatory authority;
  • +
  • Vortex or a Partner identifies a material security or operational risk; or
  • +
  • Vortex decides to cease providing the Services or any part thereof.
  • +
+

+ 19.2 Unless prohibited by law, Vortex will endeavour to provide you with notice of termination or + suspension and, where appropriate, the reasons for it. However, Vortex has no obligation to disclose details that + would compromise security or any investigation. +

+

+ 19.3 Upon termination: +

+
    +
  • your right to use the Services will cease immediately;
  • +
  • these Terms will continue to apply to any rights and obligations accrued up to the date of termination; and
  • +
  • + clauses that by their nature should survive termination (including without limitation clauses 8, 10, 11, 12, 14, 16, + 17, 18, 20, 21 and 22) will continue in full force and effect. +
  • +
+

+ 19.4 Termination of your access to the Services does not affect your obligations or rights under any + separate agreement you may have with a Partner or other third party. +

+
+ +
+

20. Changes to These Terms

+

+ 20.1 Vortex may amend or update these Terms from time to time. When we do so, we will: +

+
    +
  • publish the updated Terms on the Website; and
  • +
  • update the "Last updated" date at the top.
  • +
+

+ 20.2 Where changes are material and where reasonably practicable, we will seek to provide notice (for + example via the Website, email or within the Widget or APIs) before the new Terms take effect. +

+

+ 20.3 Your continued access to or use of the Services after the effective date of updated Terms will + constitute your acceptance of those changes. If you do not agree with the updated Terms, you must stop using the + Services. +

+
+ +
+

21. Governing Law and Dispute Resolution

+

+ 21.1 These Terms and any non-contractual obligations arising out of or in connection with them are + governed by the laws of England and Wales, without regard to its conflict of law rules. +

+

+ 21.2 Subject to any mandatory consumer protection rules in your country of residence, any dispute, + controversy or claim arising out of or in connection with these Terms or the Services, including any question + regarding their existence, validity or termination, will be resolved by the courts of England and Wales which will + have exclusive jurisdiction. +

+

+ 21.3 If you are a consumer and mandatory law gives you the right to bring claims in the courts of + your home country, nothing in these Terms will limit that right. +

+
+ +
+

22. Miscellaneous

+

+ 22.1 Entire agreement +

+

+ These Terms, together with the Privacy Policy, Cookie Policy and any other documents or policies explicitly + incorporated by reference, constitute the entire agreement between you and Vortex in relation to your use of the + Services and supersede any prior agreements or understandings. +

+

+ 22.2 Assignment +

+

+ You may not assign, transfer or delegate any of your rights or obligations under these Terms without Vortex's prior + written consent. Vortex may assign or transfer its rights and obligations under these Terms, in whole or in part, to + any affiliate or in connection with a merger, acquisition, corporate reorganisation or sale of assets, without your + consent. +

+

+ 22.3 Severability +

+

+ If any provision of these Terms is held to be invalid, illegal or unenforceable by a competent authority, that + provision will be enforced to the maximum extent permitted and the remaining provisions will continue in full force + and effect. +

+

+ 22.4 No waiver +

+

+ No failure or delay by Vortex in exercising any right or remedy under these Terms will operate as a waiver of that + right or remedy, nor will any single or partial exercise of any such right or remedy preclude any further exercise. +

+

+ 22.5 Force majeure +

+

+ Vortex will not be liable for any failure or delay in performing its obligations under these Terms to the extent + caused by events or circumstances beyond its reasonable control, including force majeure events, network failures, + third-party outages or changes in law. +

+

+ 22.6 Notices +

+

+ Notices to Vortex under these Terms should be sent to support@vortexfinance.co or to any other + contact method specified on the Website. Vortex may provide notices to you via: +

+
    +
  • the Website;
  • +
  • email to the address associated with your account (if any); or
  • +
  • in-product notifications within the Widget or APIs.
  • +
+

+ 22.7 Language +

+

+ These Terms may be made available in multiple languages. In case of discrepancies or conflicts between different + language versions, the English version will prevail, except where prohibited by applicable law. +

+
+ + ); +} diff --git a/apps/frontend/src/pages/terms/index.tsx b/apps/frontend/src/pages/terms/short/index.tsx similarity index 73% rename from apps/frontend/src/pages/terms/index.tsx rename to apps/frontend/src/pages/terms/short/index.tsx index 88c10b3dd..9e40f6cc6 100644 --- a/apps/frontend/src/pages/terms/index.tsx +++ b/apps/frontend/src/pages/terms/short/index.tsx @@ -1,14 +1,29 @@ -import { useTranslation } from "react-i18next"; +import { Link, useParams } from "@tanstack/react-router"; +import { Trans, useTranslation } from "react-i18next"; -export function TermsAndConditionsPage() { +export function TermsAndConditionsShortPage() { const { t } = useTranslation(); + const params = useParams({ strict: false }); return (

{t("pages.termsAndConditions.title")}

{t("pages.termsAndConditions.lastUpdated")}

-

{t("pages.termsAndConditions.intro")}

+

+ + ) + }} + i18nKey="pages.termsAndConditions.intro" + /> +

{t("pages.termsAndConditions.sections.1.title")}

@@ -67,7 +82,20 @@ export function TermsAndConditionsPage() {

{t("pages.termsAndConditions.sections.12.title")}

-

{t("pages.termsAndConditions.sections.12.text")}

+

+ + ) + }} + i18nKey="pages.termsAndConditions.sections.12.text" + /> +

); diff --git a/apps/frontend/src/routeTree.gen.ts b/apps/frontend/src/routeTree.gen.ts index 9aca1cd54..6b6b74ea4 100644 --- a/apps/frontend/src/routeTree.gen.ts +++ b/apps/frontend/src/routeTree.gen.ts @@ -12,6 +12,7 @@ import { Route as rootRouteImport } from './routes/__root' import { Route as Char123LocaleChar125RouteImport } from './routes/{-$locale}' import { Route as Char123LocaleChar125IndexRouteImport } from './routes/{-$locale}/index' import { Route as Char123LocaleChar125WidgetRouteImport } from './routes/{-$locale}/widget' +import { Route as Char123LocaleChar125TermsAndConditionsFullRouteImport } from './routes/{-$locale}/terms-and-conditions-full' import { Route as Char123LocaleChar125TermsAndConditionsRouteImport } from './routes/{-$locale}/terms-and-conditions' import { Route as Char123LocaleChar125PrivacyPolicyRouteImport } from './routes/{-$locale}/privacy-policy' import { Route as Char123LocaleChar125BusinessRouteImport } from './routes/{-$locale}/business' @@ -33,6 +34,12 @@ const Char123LocaleChar125WidgetRoute = path: '/widget', getParentRoute: () => Char123LocaleChar125Route, } as any) +const Char123LocaleChar125TermsAndConditionsFullRoute = + Char123LocaleChar125TermsAndConditionsFullRouteImport.update({ + id: '/terms-and-conditions-full', + path: '/terms-and-conditions-full', + getParentRoute: () => Char123LocaleChar125Route, + } as any) const Char123LocaleChar125TermsAndConditionsRoute = Char123LocaleChar125TermsAndConditionsRouteImport.update({ id: '/terms-and-conditions', @@ -57,6 +64,7 @@ export interface FileRoutesByFullPath { '/{-$locale}/business': typeof Char123LocaleChar125BusinessRoute '/{-$locale}/privacy-policy': typeof Char123LocaleChar125PrivacyPolicyRoute '/{-$locale}/terms-and-conditions': typeof Char123LocaleChar125TermsAndConditionsRoute + '/{-$locale}/terms-and-conditions-full': typeof Char123LocaleChar125TermsAndConditionsFullRoute '/{-$locale}/widget': typeof Char123LocaleChar125WidgetRoute '/{-$locale}/': typeof Char123LocaleChar125IndexRoute } @@ -64,6 +72,7 @@ export interface FileRoutesByTo { '/{-$locale}/business': typeof Char123LocaleChar125BusinessRoute '/{-$locale}/privacy-policy': typeof Char123LocaleChar125PrivacyPolicyRoute '/{-$locale}/terms-and-conditions': typeof Char123LocaleChar125TermsAndConditionsRoute + '/{-$locale}/terms-and-conditions-full': typeof Char123LocaleChar125TermsAndConditionsFullRoute '/{-$locale}/widget': typeof Char123LocaleChar125WidgetRoute '/{-$locale}': typeof Char123LocaleChar125IndexRoute } @@ -73,6 +82,7 @@ export interface FileRoutesById { '/{-$locale}/business': typeof Char123LocaleChar125BusinessRoute '/{-$locale}/privacy-policy': typeof Char123LocaleChar125PrivacyPolicyRoute '/{-$locale}/terms-and-conditions': typeof Char123LocaleChar125TermsAndConditionsRoute + '/{-$locale}/terms-and-conditions-full': typeof Char123LocaleChar125TermsAndConditionsFullRoute '/{-$locale}/widget': typeof Char123LocaleChar125WidgetRoute '/{-$locale}/': typeof Char123LocaleChar125IndexRoute } @@ -83,6 +93,7 @@ export interface FileRouteTypes { | '/{-$locale}/business' | '/{-$locale}/privacy-policy' | '/{-$locale}/terms-and-conditions' + | '/{-$locale}/terms-and-conditions-full' | '/{-$locale}/widget' | '/{-$locale}/' fileRoutesByTo: FileRoutesByTo @@ -90,6 +101,7 @@ export interface FileRouteTypes { | '/{-$locale}/business' | '/{-$locale}/privacy-policy' | '/{-$locale}/terms-and-conditions' + | '/{-$locale}/terms-and-conditions-full' | '/{-$locale}/widget' | '/{-$locale}' id: @@ -98,6 +110,7 @@ export interface FileRouteTypes { | '/{-$locale}/business' | '/{-$locale}/privacy-policy' | '/{-$locale}/terms-and-conditions' + | '/{-$locale}/terms-and-conditions-full' | '/{-$locale}/widget' | '/{-$locale}/' fileRoutesById: FileRoutesById @@ -129,6 +142,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof Char123LocaleChar125WidgetRouteImport parentRoute: typeof Char123LocaleChar125Route } + '/{-$locale}/terms-and-conditions-full': { + id: '/{-$locale}/terms-and-conditions-full' + path: '/terms-and-conditions-full' + fullPath: '/{-$locale}/terms-and-conditions-full' + preLoaderRoute: typeof Char123LocaleChar125TermsAndConditionsFullRouteImport + parentRoute: typeof Char123LocaleChar125Route + } '/{-$locale}/terms-and-conditions': { id: '/{-$locale}/terms-and-conditions' path: '/terms-and-conditions' @@ -157,6 +177,7 @@ interface Char123LocaleChar125RouteChildren { Char123LocaleChar125BusinessRoute: typeof Char123LocaleChar125BusinessRoute Char123LocaleChar125PrivacyPolicyRoute: typeof Char123LocaleChar125PrivacyPolicyRoute Char123LocaleChar125TermsAndConditionsRoute: typeof Char123LocaleChar125TermsAndConditionsRoute + Char123LocaleChar125TermsAndConditionsFullRoute: typeof Char123LocaleChar125TermsAndConditionsFullRoute Char123LocaleChar125WidgetRoute: typeof Char123LocaleChar125WidgetRoute Char123LocaleChar125IndexRoute: typeof Char123LocaleChar125IndexRoute } @@ -167,6 +188,8 @@ const Char123LocaleChar125RouteChildren: Char123LocaleChar125RouteChildren = { Char123LocaleChar125PrivacyPolicyRoute, Char123LocaleChar125TermsAndConditionsRoute: Char123LocaleChar125TermsAndConditionsRoute, + Char123LocaleChar125TermsAndConditionsFullRoute: + Char123LocaleChar125TermsAndConditionsFullRoute, Char123LocaleChar125WidgetRoute: Char123LocaleChar125WidgetRoute, Char123LocaleChar125IndexRoute: Char123LocaleChar125IndexRoute, } diff --git a/apps/frontend/src/routes/{-$locale}/terms-and-conditions-full.tsx b/apps/frontend/src/routes/{-$locale}/terms-and-conditions-full.tsx new file mode 100644 index 000000000..fd468eccb --- /dev/null +++ b/apps/frontend/src/routes/{-$locale}/terms-and-conditions-full.tsx @@ -0,0 +1,11 @@ +import { createFileRoute } from "@tanstack/react-router"; +import { BaseLayout } from "../../layouts"; +import { TermsAndConditionsFullPage } from "../../pages/terms/full"; + +export const Route = createFileRoute("/{-$locale}/terms-and-conditions-full")({ + component: TermsAndConditionsFullRouteComponent +}); + +function TermsAndConditionsFullRouteComponent() { + return } />; +} diff --git a/apps/frontend/src/routes/{-$locale}/terms-and-conditions.tsx b/apps/frontend/src/routes/{-$locale}/terms-and-conditions.tsx index 500e6cfea..c33f978ea 100644 --- a/apps/frontend/src/routes/{-$locale}/terms-and-conditions.tsx +++ b/apps/frontend/src/routes/{-$locale}/terms-and-conditions.tsx @@ -1,11 +1,11 @@ import { createFileRoute } from "@tanstack/react-router"; import { BaseLayout } from "../../layouts"; -import { TermsAndConditionsPage } from "../../pages/terms"; +import { TermsAndConditionsShortPage } from "../../pages/terms/short"; export const Route = createFileRoute("/{-$locale}/terms-and-conditions")({ - component: TermsAndConditionsRouteComponent + component: TermsAndConditionsShortRouteComponent }); -function TermsAndConditionsRouteComponent() { - return } />; +function TermsAndConditionsShortRouteComponent() { + return } />; } diff --git a/apps/frontend/src/stores/quote/useQuoteStore.ts b/apps/frontend/src/stores/quote/useQuoteStore.ts index 76fd47f89..4ef47cf17 100644 --- a/apps/frontend/src/stores/quote/useQuoteStore.ts +++ b/apps/frontend/src/stores/quote/useQuoteStore.ts @@ -75,7 +75,8 @@ const friendlyErrorMessages: Record = { [QuoteError.InputAmountForSwapMustBeGreaterThanZero]: "pages.swap.error.tryLargerAmount", [QuoteError.InputAmountTooLow]: "pages.swap.error.tryLargerAmount", [QuoteError.InputAmountTooLowToCoverCalculatedFees]: "pages.swap.error.tryLargerAmount", - + [QuoteError.BelowLowerLimitSell]: QuoteError.BelowLowerLimitSell, // We leave this as-is, as the replacement string depends on the context + [QuoteError.BelowLowerLimitBuy]: QuoteError.BelowLowerLimitBuy, // We leave this as-is, as the replacement string depends on the context // Calculation failures - suggest different amount [QuoteError.UnableToGetPendulumTokenDetails]: "pages.swap.error.tryDifferentAmount", [QuoteError.FailedToCalculateQuote]: "pages.swap.error.tryDifferentAmount", diff --git a/apps/frontend/src/translations/en.json b/apps/frontend/src/translations/en.json index 296daf20b..297266b69 100644 --- a/apps/frontend/src/translations/en.json +++ b/apps/frontend/src/translations/en.json @@ -106,7 +106,8 @@ "birthdate": { "future": "Birthdate cannot be in the future", "required": "Birthdate is required", - "tooOld": "Invalid birthdate" + "tooOld": "Invalid birthdate", + "tooYoung": "You must be at least 18 years old" }, "cep": { "minLength": "CEP must be at least 3 characters", @@ -219,6 +220,7 @@ "rgFront": "RG Front", "uploadSelfie": "Upload Selfie" }, + "helperText": "Click here to upload", "title": "Fast-Track Verification", "uploadBug": "There was an error uploading the files for verification. Please try again later.", "uploadFailed": "Upload failed. Please try again.", @@ -542,7 +544,6 @@ "titlePart2": "inside your APP", "widgetIntegration": "Widget integration" }, - "whyVortexApi": { "cta": { "npmPackage": "NPM package", @@ -877,7 +878,7 @@ "assethubToPendulum": "Bridging {{assetSymbol}} from AssetHub --> Pendulum", "bridgingEVM": "Bridging {{assetSymbol}} from {{network}} --> Moonbeam", "brlaOnrampMint": "Your payment is being processed. This can take up to 5 minutes.", - "closeProgressScreenText": "💡 You’re all set! You can now close this tab or grab a coffee while we finish up in the background.", + "closeProgressScreenText": "You’re all set! You can now close this tab or grab a coffee while we finish up in the background.", "createStellarAccount": "Creating Stellar account", "estimatedTimeAssetHub": "This usually takes 4-6 minutes.", "estimatedTimeEVM": "This usually takes 6-8 minutes.", @@ -919,6 +920,10 @@ "developedBy": "Developed by", "error": { "ARS_tokenUnavailable": "Improving your ARS exit - back shortly! ", + "amountOutOfRange": { + "buy": "{{assetSymbol}} orders must be between {{minAmountUnits}} and {{maxAmountUnits}}", + "sell": "{{assetSymbol}} orders must be between {{minAmountUnits}} and {{maxAmountUnits}}" + }, "BRL_tokenUnavailable": "Improving your BRL exit - back shortly! ", "EURC_tokenUnavailable": "Improving your EUR exit - back shortly! ", "feeComponents": "Failed to calculate the fees. Please try again with a different amount.", @@ -950,7 +955,7 @@ } }, "termsAndConditions": { - "intro": "These Short Terms provide a high-level summary of how Vortex operates. They are legally binding. A detailed version of the Terms (\"FULL TERMS & CONDITIONS\") is available and applies in full.", + "intro": "These Short Terms provide a high-level summary of how Vortex operates. They are legally binding. A detailed version of the Terms (<1>\"FULL TERMS & CONDITIONS\") is available and applies in full.", "lastUpdated": "Last updated: 7th January 2026", "sections": { "1": { @@ -998,7 +1003,7 @@ "title": "11. Governing Law" }, "12": { - "text": "These Short Terms are supplemented by the Full Terms of Service, Privacy Policy, and Partner Terms, which apply in full in all cases.", + "text": "These Short Terms are supplemented by the <1>Full Terms of Service, Privacy Policy, and Partner Terms, which apply in full in all cases.", "title": "12. Full Terms" } }, diff --git a/apps/frontend/src/translations/pt.json b/apps/frontend/src/translations/pt.json index 7154762a4..0734d69cd 100644 --- a/apps/frontend/src/translations/pt.json +++ b/apps/frontend/src/translations/pt.json @@ -107,7 +107,8 @@ "birthdate": { "future": "Data de nascimento não pode ser no futuro", "required": "Data de nascimento é obrigatória", - "tooOld": "Data de nascimento inválida" + "tooOld": "Data de nascimento inválida", + "tooYoung": "Você deve ter pelo menos 18 anos de idade" }, "cep": { "minLength": "CEP deve ter pelo menos 3 caracteres", @@ -220,6 +221,7 @@ "rgFront": "Frente do RG", "uploadSelfie": "Enviar selfie" }, + "helperText": "Clique aqui para enviar", "title": "Verificação Rápida", "uploadBug": "Ocorreu um erro ao enviar os arquivos para verificação. Por favor, tente novamente mais tarde.", "uploadFailed": "Falha no envio. Por favor, tente novamente.", @@ -870,7 +872,7 @@ "assethubToPendulum": "Transferindo {{assetSymbol}} de AssetHub --> Pendulum", "bridgingEVM": "Transferindo {{assetSymbol}} de {{network}} --> Moonbeam", "brlaOnrampMint": "Seu pagamento está sendo processado. Isso pode levar até 5 minutos.", - "closeProgressScreenText": "💡 Tudo pronto! Você já pode fechar esta aba ou pegar um café enquanto finalizamos o processo em segundo plano.", + "closeProgressScreenText": "Tudo pronto! Você já pode fechar esta aba ou pegar um café enquanto finalizamos o processo em segundo plano.", "createStellarAccount": "Criando conta Stellar", "estimatedTimeAssetHub": "Isso geralmente leva de 4 a 6 minutos.", "estimatedTimeEVM": "Isso geralmente leva de 6 a 8 minutos.", @@ -912,6 +914,10 @@ "developedBy": "Desenvolvido por", "error": { "ARS_tokenUnavailable": "Ajustando sua saída ARS - em breve!", + "amountOutOfRange": { + "buy": "Pedidos em {{assetSymbol}} devem estar entre {{minAmountUnits}} e {{maxAmountUnits}}", + "sell": "Pedidos em {{assetSymbol}} devem estar entre {{minAmountUnits}} e {{maxAmountUnits}}" + }, "BRL_tokenUnavailable": "Ajustando sua saída BRL - em breve!", "EURC_tokenUnavailable": "Ajustando sua saída EUR - em breve!", "feeComponents": "Falha ao calcular as taxas. Por favor, tente novamente com um valor diferente.", @@ -943,7 +949,7 @@ } }, "termsAndConditions": { - "intro": "Estes Termos Curtos fornecem um resumo de alto nível de como a Vortex opera. Eles são juridicamente vinculativos. Uma versão detalhada dos Termos (\"TERMOS E CONDIÇÕES COMPLETOS\") está disponível e se aplica integralmente.", + "intro": "Estes Termos Curtos fornecem um resumo de alto nível de como a Vortex opera. Eles são juridicamente vinculativos. Uma versão detalhada dos Termos (<1>\"TERMOS E CONDIÇÕES COMPLETOS\") está disponível e se aplica integralmente.", "lastUpdated": "Última atualização: 7 de Janeiro de 2026", "sections": { "1": { @@ -991,7 +997,7 @@ "title": "11. Lei Aplicável" }, "12": { - "text": "Estes Termos Curtos são complementados pelos Termos de Serviço Completos, Política de Privacidade e Termos do Parceiro, que se aplicam integralmente em todos os casos.", + "text": "Estes Termos Curtos são complementados pelos <1>Termos de Serviço Completos, Política de Privacidade e Termos do Parceiro, que se aplicam integralmente em todos os casos.", "title": "12. Termos Completos" } }, diff --git a/packages/shared/src/endpoints/quote.endpoints.ts b/packages/shared/src/endpoints/quote.endpoints.ts index 924b72c61..265fe0550 100644 --- a/packages/shared/src/endpoints/quote.endpoints.ts +++ b/packages/shared/src/endpoints/quote.endpoints.ts @@ -95,6 +95,8 @@ export enum QuoteError { InputAmountForSwapMustBeGreaterThanZero = "Input amount for swap must be greater than 0", InputAmountTooLow = "Input amount too low. Please try a larger amount.", InputAmountTooLowToCoverCalculatedFees = "Input amount too low to cover calculated fees.", + BelowLowerLimitSell = "Output amount below minimum SELL limit of", + BelowLowerLimitBuy = "Input amount below minimum BUY limit of", // Token/calculation errors UnableToGetPendulumTokenDetails = "Unable to get Pendulum token details", diff --git a/packages/shared/src/services/brla/helpers.ts b/packages/shared/src/services/brla/helpers.ts index 6b70b4104..18df512fa 100644 --- a/packages/shared/src/services/brla/helpers.ts +++ b/packages/shared/src/services/brla/helpers.ts @@ -18,10 +18,64 @@ export function generateReferenceLabel(quote: Quote): string { export const CPF_REGEX = /^\d{3}(\.\d{3}){2}-\d{2}$|^\d{11}$/; export const CNPJ_REGEX = /^(\d{2}\.?\d{3}\.?\d{3}\/?\d{4}-?\d{2})$/; +/** + * Checks if all digits in a string are the same (e.g., "11111111111") + */ +function hasAllSameDigits(digits: string): boolean { + if (digits.length === 0) return false; + const firstDigit = digits[0]; + return digits.split("").every(d => d === firstDigit); +} + +/** + * Checks if digits form an ascending sequence (e.g., "12345678901" or "01234567890") + */ +function isAscendingSequence(digits: string): boolean { + for (let i = 1; i < digits.length; i++) { + const prev = parseInt(digits[i - 1], 10); + const curr = parseInt(digits[i], 10); + // Allow wrap-around from 9 to 0 + if (curr !== (prev + 1) % 10) { + return false; + } + } + return true; +} + +/** + * Checks if digits form a descending sequence (e.g., "98765432109" or "10987654321") + */ +function isDescendingSequence(digits: string): boolean { + for (let i = 1; i < digits.length; i++) { + const prev = parseInt(digits[i - 1], 10); + const curr = parseInt(digits[i], 10); + // Allow wrap-around from 0 to 9 + if (curr !== (prev - 1 + 10) % 10) { + return false; + } + } + return true; +} + +/** + * Checks if the input contains a trivial pattern (all same digits or sequential) + */ +function isTrivialPattern(input: string): boolean { + // Extract only digits + const digits = input.replace(/\D/g, ""); + if (digits.length === 0) return false; + + return hasAllSameDigits(digits) || isAscendingSequence(digits) || isDescendingSequence(digits); +} + export function isValidCnpj(cnpj: string): boolean { - return CNPJ_REGEX.test(cnpj); + if (!CNPJ_REGEX.test(cnpj)) return false; + if (isTrivialPattern(cnpj)) return false; + return true; } export function isValidCpf(cpf: string): boolean { - return CPF_REGEX.test(cpf); + if (!CPF_REGEX.test(cpf)) return false; + if (isTrivialPattern(cpf)) return false; + return true; }