+
+
{t("components.moneriumRedirect.description")}
-
+
@@ -39,7 +40,7 @@ export function MoneriumRedirectStep({ className }: MoneriumRedirectStepProps) {
>
{t("components.moneriumRedirect.goToPartner")}
-
+
);
}
diff --git a/apps/frontend/src/components/widget-steps/SummaryStep/index.tsx b/apps/frontend/src/components/widget-steps/SummaryStep/index.tsx
index 8b5c0892c..9dbdfdd2c 100644
--- a/apps/frontend/src/components/widget-steps/SummaryStep/index.tsx
+++ b/apps/frontend/src/components/widget-steps/SummaryStep/index.tsx
@@ -10,6 +10,7 @@ import { useRampSummaryActions } from "../../../stores/rampSummary";
import { MenuButtons } from "../../MenuButtons";
import { RampSubmitButton } from "../../RampSubmitButton/RampSubmitButton";
import { SigningBoxButton, SigningBoxContent } from "../../SigningBox/SigningBoxContent";
+import { StepFooter } from "../../StepFooter";
import { TransactionTokensDisplay } from "./TransactionTokensDisplay";
export const SummaryStep: FC = () => {
@@ -114,11 +115,13 @@ export const SummaryStep: FC = () => {
);
return (
-
+
-
{headerText}
- {content}
-
{actions}
+
+
{headerText}
+
{content}
+
+
{actions}
);
};
diff --git a/apps/frontend/src/hooks/quote/schema.ts b/apps/frontend/src/hooks/quote/schema.ts
index 7bd3d8cd6..f7f6a199d 100644
--- a/apps/frontend/src/hooks/quote/schema.ts
+++ b/apps/frontend/src/hooks/quote/schema.ts
@@ -1,4 +1,4 @@
-import { FiatToken, OnChainToken, RampDirection } from "@vortexfi/shared";
+import { FiatToken, OnChainToken, OnChainTokenSymbol, RampDirection } from "@vortexfi/shared";
import { useTranslation } from "react-i18next";
import * as Yup from "yup";
import { useRampDirection } from "../../stores/rampDirectionStore";
@@ -6,7 +6,7 @@ import { useRampDirection } from "../../stores/rampDirectionStore";
export type QuoteFormValues = {
inputAmount: string;
outputAmount?: string;
- onChainToken: OnChainToken;
+ onChainToken: OnChainTokenSymbol;
fiatToken: FiatToken;
slippage?: number;
deadline?: number;
diff --git a/apps/frontend/src/hooks/quote/useQuoteService.ts b/apps/frontend/src/hooks/quote/useQuoteService.ts
index 5f71280d8..f82e5f799 100644
--- a/apps/frontend/src/hooks/quote/useQuoteService.ts
+++ b/apps/frontend/src/hooks/quote/useQuoteService.ts
@@ -1,4 +1,4 @@
-import { FiatToken, OnChainToken } from "@vortexfi/shared";
+import { FiatToken, OnChainTokenSymbol } from "@vortexfi/shared";
import Big from "big.js";
import { useCallback, useEffect } from "react";
import { useEventsContext } from "../../contexts/events";
@@ -13,7 +13,7 @@ import { useRampDirection } from "../../stores/rampDirectionStore";
// if you don't want to get a new quote - you get outputAmount through useQuoteStore
// This is not optimal, and introduce too much cognitive load
-export const useQuoteService = (inputAmount: string | undefined, onChainToken: OnChainToken, fiatToken: FiatToken) => {
+export const useQuoteService = (inputAmount: string | undefined, onChainToken: OnChainTokenSymbol, fiatToken: FiatToken) => {
const { trackEvent } = useEventsContext();
const { selectedNetwork } = useNetwork();
const rampType = useRampDirection();
diff --git a/apps/frontend/src/hooks/ramp/useIsQuoteComponentDisplayed.ts b/apps/frontend/src/hooks/ramp/useIsQuoteComponentDisplayed.ts
index 6b7433335..682eb4261 100644
--- a/apps/frontend/src/hooks/ramp/useIsQuoteComponentDisplayed.ts
+++ b/apps/frontend/src/hooks/ramp/useIsQuoteComponentDisplayed.ts
@@ -1,29 +1,36 @@
+import { RampSearchParams } from "./../../types/searchParams";
import { useRampComponentState } from "./useRampComponentState";
-import { hasAllQuoteRefreshParams } from "./useRampNavigation";
+
+function isExternalFlow(params: RampSearchParams): boolean {
+ return !!params.externalSessionId;
+}
/**
* Hook to determine if the Quote component is currently displayed in the Ramp page.
* Mirrors the logic from useRampNavigation to check if quoteComponent would be returned.
*/
export const useIsQuoteComponentDisplayed = (): boolean => {
- const { searchParams, rampState, rampMachineState } = useRampComponentState();
+ const { rampState, rampMachineState, searchParams } = useRampComponentState();
- // Quote is NOT shown if quoteId exists in URL or all quote refresh params are present
- if (searchParams.quoteId || hasAllQuoteRefreshParams(searchParams)) {
+ if (rampState?.ramp?.currentPhase === "complete") {
return false;
}
- // Quote is NOT shown if in complete phase (shows success)
- if (rampState?.ramp?.currentPhase === "complete") {
+ if (rampState?.ramp?.currentPhase === "failed") {
return false;
}
- // Quote is NOT shown if in failed phase (shows failure)
- if (rampState?.ramp?.currentPhase === "failed") {
+ // Automatic Quote Refresh Ramp
+ if (isExternalFlow(searchParams) && (rampMachineState.value === "LoadingQuote" || rampMachineState.value === "QuoteReady")) {
return false;
}
- if (rampState === undefined && rampMachineState.value === "Idle") {
+ if (
+ (rampMachineState.value === "Idle" ||
+ rampMachineState.value === "LoadingQuote" ||
+ rampMachineState.value === "QuoteReady") &&
+ !searchParams.quoteId
+ ) {
return true;
}
diff --git a/apps/frontend/src/hooks/ramp/useRampNavigation.ts b/apps/frontend/src/hooks/ramp/useRampNavigation.ts
index 863647462..dfea88477 100644
--- a/apps/frontend/src/hooks/ramp/useRampNavigation.ts
+++ b/apps/frontend/src/hooks/ramp/useRampNavigation.ts
@@ -1,18 +1,7 @@
-import { ReactNode, useCallback, useMemo } from "react";
-import { RampSearchParams } from "../../types/searchParams";
+import { ReactNode, useCallback } from "react";
+import { useIsQuoteComponentDisplayed } from "./useIsQuoteComponentDisplayed";
import { useRampComponentState } from "./useRampComponentState";
-/**
- * Checks if all required URL parameters are present for automatic quote creation.
- * When these params are present, the Quote selection form should be skipped.
- *
- * Required params: cryptoLocked, fiat, inputAmount, network, rampType
- * These match the params checked in useRampUrlParams.ts for auto-creating a quote.
- */
-export const hasAllQuoteRefreshParams = (params: RampSearchParams): boolean => {
- return Boolean(params.cryptoLocked && params.fiat && params.inputAmount && params.network && params.rampType);
-};
-
export const useRampNavigation = (
successComponent: ReactNode,
failureComponent: ReactNode,
@@ -20,9 +9,8 @@ export const useRampNavigation = (
formComponent: ReactNode,
quoteComponent: ReactNode
) => {
- const { searchParams, rampState, rampMachineState } = useRampComponentState();
-
- const shouldSkipQuoteForm = useMemo(() => searchParams.quoteId || hasAllQuoteRefreshParams(searchParams), [searchParams]);
+ const { rampState, rampMachineState } = useRampComponentState();
+ const isQuoteDisplayed = useIsQuoteComponentDisplayed();
const getCurrentComponent = useCallback(() => {
if (rampState?.ramp?.currentPhase === "complete") {
@@ -37,11 +25,7 @@ export const useRampNavigation = (
return progressComponent;
}
- if (shouldSkipQuoteForm) {
- return formComponent;
- }
-
- if (rampMachineState.value === "Idle") {
+ if (isQuoteDisplayed) {
return quoteComponent;
}
@@ -49,12 +33,12 @@ export const useRampNavigation = (
}, [
rampState,
rampMachineState.value,
- shouldSkipQuoteForm,
successComponent,
failureComponent,
progressComponent,
formComponent,
- quoteComponent
+ quoteComponent,
+ isQuoteDisplayed
]);
return {
diff --git a/apps/frontend/src/hooks/useRampUrlParams.ts b/apps/frontend/src/hooks/useRampUrlParams.ts
index 8cd96bb1b..bc54071e7 100644
--- a/apps/frontend/src/hooks/useRampUrlParams.ts
+++ b/apps/frontend/src/hooks/useRampUrlParams.ts
@@ -2,16 +2,22 @@ import {
AssetHubToken,
DestinationType,
EPaymentMethod,
+ type EvmNetworks,
EvmToken,
FiatToken,
+ getEvmTokenConfig,
+ getEvmTokensLoadedSnapshot,
+ isNetworkEVM,
Networks,
OnChainToken,
+ OnChainTokenSymbol,
PaymentMethod,
QuoteResponse,
- RampDirection
+ RampDirection,
+ subscribeEvmTokensLoaded
} from "@vortexfi/shared";
import Big from "big.js";
-import { useCallback, useEffect, useMemo, useRef } from "react";
+import { useCallback, useEffect, useMemo, useRef, useSyncExternalStore } from "react";
import { getFirstEnabledFiatToken, isFiatTokenEnabled } from "../config/tokenAvailability";
import { useNetwork } from "../contexts/network";
import { useRampActor } from "../contexts/rampState";
@@ -33,7 +39,7 @@ interface RampUrlParams {
moneriumCode?: string;
fiat?: FiatToken;
countryCode?: string;
- cryptoLocked?: OnChainToken;
+ cryptoLocked?: OnChainTokenSymbol;
paymentMethod?: PaymentMethod;
walletLocked?: string;
callbackUrl?: string;
@@ -58,7 +64,7 @@ function findFiatToken(fiatToken?: string): FiatToken | undefined {
return foundToken;
}
-function findOnChainToken(tokenStr?: string, networkType?: Networks | string): OnChainToken | undefined {
+function findOnChainToken(tokenStr?: string, networkType?: Networks | string): OnChainTokenSymbol | undefined {
if (!tokenStr || !networkType) {
return undefined;
}
@@ -76,15 +82,15 @@ function findOnChainToken(tokenStr?: string, networkType?: Networks | string): O
const [_, tokenValue] = matchedToken;
return tokenValue as unknown as OnChainToken;
} else {
- const evmTokenEntries = Object.entries(EvmToken);
- const matchedToken = evmTokenEntries.find(([_, token]) => token.toUpperCase() === tokenStr);
-
- if (!matchedToken) {
- return EvmToken.USDC;
+ if (isNetworkEVM(networkType as Networks)) {
+ const dynamicConfig = getEvmTokenConfig();
+ const networkTokens = dynamicConfig[networkType as EvmNetworks];
+ if (networkTokens && tokenStr in networkTokens) {
+ return tokenStr;
+ }
}
- const [_, tokenValue] = matchedToken;
- return tokenValue as OnChainToken;
+ return EvmToken.USDC;
}
}
@@ -96,6 +102,12 @@ function getNetworkFromParam(param?: string): Networks | undefined {
return undefined;
}
+function stripSurroundingQuotes(str?: string | null): string | null {
+ // TanStack Router JSON-serializes strings, so inputAmount=%22100%22 decodes to "100"
+ // with literal quote chars via raw URLSearchParams.
+ return str?.replace(/^["']|["']$/g, "") || null;
+}
+
const mapFiatToDestination = (fiatToken: FiatToken): DestinationType => {
const destinationMap: Record
= {
ARS: EPaymentMethod.CBU,
@@ -108,7 +120,7 @@ const mapFiatToDestination = (fiatToken: FiatToken): DestinationType => {
interface QuoteParams {
inputAmount?: Big;
- onChainToken: OnChainToken;
+ onChainToken: OnChainTokenSymbol;
fiatToken: FiatToken;
selectedNetwork: DestinationType;
rampType: RampDirection;
@@ -119,8 +131,8 @@ interface QuotePayload {
fromDestination: DestinationType;
toDestination: DestinationType;
inputAmount: string;
- inputCurrency: OnChainToken | FiatToken;
- outputCurrency: OnChainToken | FiatToken;
+ inputCurrency: OnChainTokenSymbol | FiatToken;
+ outputCurrency: OnChainTokenSymbol | FiatToken;
}
const createQuotePayload = (params: QuoteParams): QuotePayload => {
@@ -168,25 +180,31 @@ export enum RampUrlParamsKeys {
}
export const useRampUrlParams = (): RampUrlParams => {
+ console.log("useRampUrlParams");
const params = useMemo(() => new URLSearchParams(window.location.search), []);
const { selectedNetwork } = useNetwork();
const rampDirectionStore = useRampDirection();
+ const evmTokensLoaded = useSyncExternalStore(subscribeEvmTokensLoaded, getEvmTokensLoadedSnapshot);
const urlParams = useMemo(() => {
const rampDirectionParam = params.get(RampUrlParamsKeys.RAMP_TYPE)?.toUpperCase();
+ const fiatParam = params.get(RampUrlParamsKeys.FIAT)?.toUpperCase();
+ const cryptoLockedParam = params.get(RampUrlParamsKeys.CRYPTO_LOCKED)?.toUpperCase();
+ const countryCodeParam = params.get(RampUrlParamsKeys.COUNTRY_CODE)?.toUpperCase();
+
+ const moneriumCode = params.get(RampUrlParamsKeys.MONERIUM_CODE)?.toLowerCase();
const networkParam = params.get(RampUrlParamsKeys.NETWORK)?.toLowerCase();
- const inputAmountParam = params.get(RampUrlParamsKeys.INPUT_AMOUNT);
+ const providedQuoteId = params.get(RampUrlParamsKeys.PROVIDED_QUOTE_ID)?.toLowerCase();
+ const paymentMethodParam = params.get(RampUrlParamsKeys.PAYMENT_METHOD)?.toLowerCase() as PaymentMethod | undefined;
+
+ const rawInputAmount = params.get(RampUrlParamsKeys.INPUT_AMOUNT);
+ const inputAmountParam = stripSurroundingQuotes(rawInputAmount);
+
const partnerIdParam = params.get(RampUrlParamsKeys.PARTNER_ID);
const apiKeyParam = params.get(RampUrlParamsKeys.API_KEY);
- const moneriumCode = params.get(RampUrlParamsKeys.MONERIUM_CODE)?.toLowerCase();
- const providedQuoteId = params.get(RampUrlParamsKeys.PROVIDED_QUOTE_ID)?.toLowerCase();
- const fiatParam = params.get(RampUrlParamsKeys.FIAT)?.toUpperCase();
- const cryptoLockedParam = params.get(RampUrlParamsKeys.CRYPTO_LOCKED)?.toUpperCase();
- const paymentMethodParam = params.get(RampUrlParamsKeys.PAYMENT_METHOD) as PaymentMethod | undefined;
const walletLockedParam = params.get(RampUrlParamsKeys.WALLET_LOCKED);
const callbackUrlParam = params.get(RampUrlParamsKeys.CALLBACK_URL);
const externalSessionIdParam = params.get(RampUrlParamsKeys.EXTERNAL_SESSION_ID);
- const countryCodeParam = params.get(RampUrlParamsKeys.COUNTRY_CODE)?.toUpperCase();
const rampDirection =
rampDirectionParam === RampDirection.BUY || rampDirectionParam === RampDirection.SELL
@@ -202,6 +220,7 @@ export const useRampUrlParams = (): RampUrlParams => {
callbackUrl: callbackUrlParam || undefined,
countryCode: countryCodeParam || undefined,
cryptoLocked,
+ evmTokensLoaded,
externalSessionId: externalSessionIdParam || undefined,
fiat,
inputAmount: inputAmountParam || undefined,
@@ -213,7 +232,8 @@ export const useRampUrlParams = (): RampUrlParams => {
rampDirection,
walletLocked: walletLockedParam || undefined
};
- }, [params, rampDirectionStore, selectedNetwork]);
+ // evmTokensLoaded: triggers re-evaluation of cryptoLocked when dynamic tokens (e.g. WETH, WBTC) finish loading from SquidRouter
+ }, [params, rampDirectionStore, selectedNetwork, evmTokensLoaded]);
return urlParams;
};
@@ -411,4 +431,10 @@ export const useSetRampUrlParams = () => {
handleFiatToken,
moneriumCode
]);
+
+ useEffect(() => {
+ if (cryptoLocked) {
+ setOnChainToken(cryptoLocked);
+ }
+ }, [cryptoLocked, setOnChainToken]);
};
diff --git a/apps/frontend/src/hooks/useSyncFormToUrl.ts b/apps/frontend/src/hooks/useSyncFormToUrl.ts
new file mode 100644
index 000000000..d5b996de9
--- /dev/null
+++ b/apps/frontend/src/hooks/useSyncFormToUrl.ts
@@ -0,0 +1,40 @@
+import { useNavigate, useSearch } from "@tanstack/react-router";
+import { useEffect } from "react";
+import { useNetwork } from "../contexts/network";
+import { useFiatToken, useInputAmount, useOnChainToken } from "../stores/quote/useQuoteFormStore";
+import { useRampDirection } from "../stores/rampDirectionStore";
+
+export const useSyncFormToUrl = () => {
+ const inputAmount = useInputAmount();
+ const onChainToken = useOnChainToken();
+ const fiatToken = useFiatToken();
+ const rampDirection = useRampDirection();
+ const { selectedNetwork } = useNetwork();
+ const navigate = useNavigate();
+ const searchParams = useSearch({ strict: false }) as Record;
+
+ useEffect(() => {
+ const newValues: Record = {
+ cryptoLocked: onChainToken,
+ fiat: fiatToken,
+ inputAmount: inputAmount || undefined,
+ network: selectedNetwork,
+ rampType: rampDirection
+ };
+
+ const alreadyInSync = Object.entries(newValues).every(
+ ([key, value]) => (value === undefined && !(key in searchParams)) || String(searchParams[key] ?? "") === value
+ );
+
+ if (alreadyInSync) return;
+
+ navigate({
+ replace: true,
+ search: {
+ ...searchParams,
+ ...newValues
+ },
+ to: "."
+ });
+ }, [inputAmount, onChainToken, fiatToken, rampDirection, selectedNetwork, navigate, searchParams]);
+};
diff --git a/apps/frontend/src/pages/quote/index.tsx b/apps/frontend/src/pages/quote/index.tsx
index 509fc3037..eb1ef3685 100644
--- a/apps/frontend/src/pages/quote/index.tsx
+++ b/apps/frontend/src/pages/quote/index.tsx
@@ -4,9 +4,11 @@ import { PoweredBy } from "../../components/PoweredBy";
import { Offramp } from "../../components/Ramp/Offramp";
import { Onramp } from "../../components/Ramp/Onramp";
import { RampToggle } from "../../components/RampToggle";
+import { useSyncFormToUrl } from "../../hooks/useSyncFormToUrl";
import { useRampDirection, useRampDirectionToggle } from "../../stores/rampDirectionStore";
export const Quote = () => {
+ useSyncFormToUrl();
const activeSwapDirection = useRampDirection();
const onSwapDirectionToggle = useRampDirectionToggle();
diff --git a/apps/frontend/src/sections/business/WhyVortexWidget/index.tsx b/apps/frontend/src/sections/business/WhyVortexWidget/index.tsx
index 06afadc6f..4e5d5449d 100644
--- a/apps/frontend/src/sections/business/WhyVortexWidget/index.tsx
+++ b/apps/frontend/src/sections/business/WhyVortexWidget/index.tsx
@@ -105,12 +105,14 @@ export const WhyVortexWidget = () => {
diff --git a/apps/frontend/src/services/api/quote.service.ts b/apps/frontend/src/services/api/quote.service.ts
index 4d6e4faf5..56540f595 100644
--- a/apps/frontend/src/services/api/quote.service.ts
+++ b/apps/frontend/src/services/api/quote.service.ts
@@ -3,9 +3,10 @@ import {
DestinationType,
FiatToken,
getNetworkFromDestination,
- OnChainToken,
+ OnChainTokenSymbol,
PaymentMethod,
QuoteResponse,
+ RampCurrency,
RampDirection
} from "@vortexfi/shared";
import { apiRequest } from "./api-client";
@@ -35,8 +36,8 @@ export class QuoteService {
from: DestinationType,
to: DestinationType,
inputAmount: string,
- inputCurrency: OnChainToken | FiatToken,
- outputCurrency: OnChainToken | FiatToken,
+ inputCurrency: OnChainTokenSymbol | FiatToken,
+ outputCurrency: OnChainTokenSymbol | FiatToken,
apiKey?: string,
partnerId?: string,
paymentMethod?: PaymentMethod,
@@ -52,9 +53,9 @@ export class QuoteService {
countryCode,
from,
inputAmount,
- inputCurrency,
+ inputCurrency: inputCurrency as RampCurrency,
network,
- outputCurrency,
+ outputCurrency: outputCurrency as RampCurrency,
paymentMethod,
rampType,
to
diff --git a/apps/frontend/src/services/signingService.tsx b/apps/frontend/src/services/signingService.tsx
index 17aba3b78..ee5975636 100644
--- a/apps/frontend/src/services/signingService.tsx
+++ b/apps/frontend/src/services/signingService.tsx
@@ -20,9 +20,6 @@ interface SignerServiceSep10Response {
masterClientPublic: string;
}
-type BrlaOfframpState = "BURN" | "MONEY-TRANSFER";
-type OfframpStatus = "QUEUED" | "POSTED" | "SUCCESS" | "FAILED";
-
export enum KycStatus {
PENDING = "PENDING",
REJECTED = "REJECTED",
@@ -31,11 +28,6 @@ export enum KycStatus {
export type KycStatusType = keyof typeof KycStatus;
-interface BrlaOfframpStatus {
- type: BrlaOfframpState;
- status: OfframpStatus;
-}
-
type TaxIdType = "CPF" | "CNPJ";
export interface RegisterSubaccountPayload {
diff --git a/apps/frontend/src/stores/quote/useQuoteFormStore.ts b/apps/frontend/src/stores/quote/useQuoteFormStore.ts
index 6df6457f0..607a8774f 100644
--- a/apps/frontend/src/stores/quote/useQuoteFormStore.ts
+++ b/apps/frontend/src/stores/quote/useQuoteFormStore.ts
@@ -5,6 +5,7 @@ import {
getOnChainTokenDetails,
Networks,
OnChainToken,
+ OnChainTokenSymbol,
RampDirection
} from "@vortexfi/shared";
import { create } from "zustand";
@@ -42,7 +43,7 @@ const defaultOnChainToken =
interface RampFormState {
inputAmount: string;
- onChainToken: OnChainToken;
+ onChainToken: OnChainTokenSymbol;
fiatToken: FiatToken;
lastConstraintDirection: RampDirection;
taxId?: string;
@@ -52,7 +53,7 @@ interface RampFormState {
interface RampFormActions {
actions: {
setInputAmount: (amount?: string) => void;
- setOnChainToken: (token: OnChainToken) => void;
+ setOnChainToken: (token: OnChainTokenSymbol) => void;
setFiatToken: (token: FiatToken) => void;
setConstraintDirection: (direction: RampDirection) => void;
handleNetworkChange: (network: Networks) => void;
@@ -93,7 +94,7 @@ export const useQuoteFormStore = create
()(
setConstraintDirection: (direction: RampDirection) => set({ lastConstraintDirection: direction }),
setFiatToken: (token: FiatToken) => set({ fiatToken: token }),
setInputAmount: (amount?: string) => set({ inputAmount: amount }),
- setOnChainToken: (token: OnChainToken) => set({ onChainToken: token }),
+ setOnChainToken: (token: OnChainTokenSymbol) => set({ onChainToken: token }),
setPixId: (pixId: string) => set({ pixId }),
setTaxId: (taxId: string) => set({ taxId })
}
diff --git a/apps/frontend/src/stores/quote/useQuoteStore.ts b/apps/frontend/src/stores/quote/useQuoteStore.ts
index 9cf217466..24f25958f 100644
--- a/apps/frontend/src/stores/quote/useQuoteStore.ts
+++ b/apps/frontend/src/stores/quote/useQuoteStore.ts
@@ -3,6 +3,7 @@ import {
EPaymentMethod,
FiatToken,
OnChainToken,
+ OnChainTokenSymbol,
QuoteError,
QuoteResponse,
RampDirection
@@ -14,7 +15,7 @@ import { QuoteService } from "../../services/api";
interface QuoteParams {
inputAmount?: Big;
- onChainToken: OnChainToken;
+ onChainToken: OnChainTokenSymbol;
fiatToken: FiatToken;
selectedNetwork: DestinationType;
rampType: RampDirection;
@@ -27,8 +28,8 @@ interface QuotePayload {
fromDestination: DestinationType;
toDestination: DestinationType;
inputAmount: string;
- inputCurrency: OnChainToken | FiatToken;
- outputCurrency: OnChainToken | FiatToken;
+ inputCurrency: OnChainTokenSymbol | FiatToken;
+ outputCurrency: OnChainTokenSymbol | FiatToken;
}
interface QuoteActions {
diff --git a/apps/frontend/src/types/phases.ts b/apps/frontend/src/types/phases.ts
index ab8a3d221..0a0535f62 100644
--- a/apps/frontend/src/types/phases.ts
+++ b/apps/frontend/src/types/phases.ts
@@ -2,7 +2,7 @@ import {
EphemeralAccount,
FiatToken,
Networks,
- OnChainToken,
+ OnChainTokenSymbol,
PaymentData,
PresignedTx,
QuoteResponse,
@@ -24,7 +24,7 @@ export interface RampState {
export interface RampExecutionInput {
quote: QuoteResponse;
- onChainToken: OnChainToken;
+ onChainToken: OnChainTokenSymbol;
fiatToken: FiatToken;
sourceOrDestinationAddress: string; // The source address for offramps, destination address for onramps
moneriumWalletAddress?: string; // Only needed for Monerium offramps to non-EVM chains (e.g. Monerium -> Assethub)
diff --git a/apps/frontend/src/types/searchParams.ts b/apps/frontend/src/types/searchParams.ts
index 15fc59cb5..9f8a2cae7 100644
--- a/apps/frontend/src/types/searchParams.ts
+++ b/apps/frontend/src/types/searchParams.ts
@@ -1,4 +1,3 @@
-import { EPaymentMethod, RampDirection } from "@vortexfi/shared";
import { z } from "zod";
/**
@@ -22,9 +21,9 @@ export const rampSearchSchema = z.object({
inputAmount: stringOrNumberParam,
network: z.string().optional(),
partnerId: z.string().optional(),
- paymentMethod: z.nativeEnum(EPaymentMethod).optional().catch(undefined),
+ paymentMethod: z.string().optional(),
quoteId: z.string().optional(),
- rampType: z.nativeEnum(RampDirection).optional().catch(undefined),
+ rampType: z.string().optional(),
walletAddressLocked: z.string().optional()
});
diff --git a/packages/shared/src/tokens/evm/dynamicEvmTokens.ts b/packages/shared/src/tokens/evm/dynamicEvmTokens.ts
index d2a6640e3..039cdbb1b 100644
--- a/packages/shared/src/tokens/evm/dynamicEvmTokens.ts
+++ b/packages/shared/src/tokens/evm/dynamicEvmTokens.ts
@@ -45,6 +45,19 @@ const state: DynamicEvmTokensState = {
tokensByNetwork: {} as Record>>
};
+const evmTokenListeners = new Set<() => void>();
+
+export function subscribeEvmTokensLoaded(onStoreChange: () => void): () => void {
+ evmTokenListeners.add(onStoreChange);
+ return () => {
+ evmTokenListeners.delete(onStoreChange);
+ };
+}
+
+export function getEvmTokensLoadedSnapshot(): boolean {
+ return state.isLoaded;
+}
+
/**
* Iterates over all EVM networks and calls the callback for each.
*/
@@ -283,13 +296,26 @@ export async function initializeEvmTokens(): Promise {
state.tokensByNetwork = mergeWithStaticConfig(groupedTokens);
state.priceBySymbol = buildPriceLookup(state.tokensByNetwork);
state.isLoaded = true;
+ for (const listener of evmTokenListeners) {
+ try {
+ listener();
+ } catch (listenerErr) {
+ logger.current.error("[DynamicEvmTokens] Error in EVM token listener", listenerErr);
+ }
+ }
} catch (err) {
console.error("[DynamicEvmTokens] Failed to fetch tokens from SquidRouter, using fallback:", err);
-
state.tokensByNetwork = buildFallbackFromStaticConfig();
state.priceBySymbol = buildPriceLookup(state.tokensByNetwork);
state.isLoaded = true;
}
+ for (const listener of evmTokenListeners) {
+ try {
+ listener();
+ } catch (listenerErr) {
+ logger.current.error("[DynamicEvmTokens] Error in EVM token listener", listenerErr);
+ }
+ }
}
/**
diff --git a/packages/shared/src/tokens/types/base.ts b/packages/shared/src/tokens/types/base.ts
index 6d2c0b1fc..19250c380 100644
--- a/packages/shared/src/tokens/types/base.ts
+++ b/packages/shared/src/tokens/types/base.ts
@@ -20,6 +20,8 @@ export enum AssetHubToken {
}
export type OnChainToken = EvmToken | AssetHubToken;
+/** Includes dynamic tokens (e.g. WETH, WBTC) loaded at runtime from SquidRouter */
+export type OnChainTokenSymbol = OnChainToken | (string & {});
export type NablaToken = OnChainToken;
// Combines fiat currencies with tokens in one type
diff --git a/packages/shared/src/tokens/utils/helpers.ts b/packages/shared/src/tokens/utils/helpers.ts
index 6fd5d4a0c..82f3b2de8 100644
--- a/packages/shared/src/tokens/utils/helpers.ts
+++ b/packages/shared/src/tokens/utils/helpers.ts
@@ -9,7 +9,7 @@ import { evmTokenConfig } from "../evm/config";
import { getEvmTokenConfig } from "../evm/dynamicEvmTokens";
import { moonbeamTokenConfig } from "../moonbeam/config";
import { stellarTokenConfig } from "../stellar/config";
-import { AssetHubToken, FiatToken, OnChainToken, RampCurrency } from "../types/base";
+import { AssetHubToken, FiatToken, OnChainToken, OnChainTokenSymbol, RampCurrency } from "../types/base";
import { EvmToken, EvmTokenDetails } from "../types/evm";
import { MoonbeamTokenDetails } from "../types/moonbeam";
import { PendulumTokenDetails } from "../types/pendulum";
@@ -22,7 +22,7 @@ import { FiatTokenDetails, OnChainTokenDetails } from "./typeGuards";
*/
export function getOnChainTokenDetails(
network: Networks,
- onChainToken: OnChainToken,
+ onChainToken: OnChainTokenSymbol,
dynamicEvmTokenConfig?: Record>>
): OnChainTokenDetails | undefined {
const normalizedOnChainToken = normalizeTokenSymbol(onChainToken);
@@ -51,7 +51,7 @@ export function getOnChainTokenDetails(
*/
export function getOnChainTokenDetailsOrDefault(
network: Networks,
- onChainToken: OnChainToken,
+ onChainToken: OnChainTokenSymbol,
dynamicEvmTokenConfig?: Record>>
): OnChainTokenDetails {
// AXLUSDC doesn't exist Ethereum
@@ -67,7 +67,6 @@ export function getOnChainTokenDetailsOrDefault(
return maybeOnChainTokenDetails;
}
- logger.current.error(`Invalid input token type: ${onChainToken}`);
if (network === Networks.AssetHub) {
const firstAvailableToken = Object.values(assetHubTokenConfig)[0];
if (!firstAvailableToken) {