-
+
+
- {userEmail}
+ {userEmail}
-
+
>
)}
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/useRampUrlParams.ts b/apps/frontend/src/hooks/useRampUrlParams.ts
index 8cd96bb1b..a2993d04a 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;
}
}
@@ -108,7 +114,7 @@ const mapFiatToDestination = (fiatToken: FiatToken): DestinationType => {
interface QuoteParams {
inputAmount?: Big;
- onChainToken: OnChainToken;
+ onChainToken: OnChainTokenSymbol;
fiatToken: FiatToken;
selectedNetwork: DestinationType;
rampType: RampDirection;
@@ -119,8 +125,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 => {
@@ -171,22 +177,25 @@ export const useRampUrlParams = (): RampUrlParams => {
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 providedQuoteId = params.get(RampUrlParamsKeys.PROVIDED_QUOTE_ID)?.toLowerCase();
+ const paymentMethodParam = params.get(RampUrlParamsKeys.PAYMENT_METHOD)?.toLowerCase() as PaymentMethod | undefined;
+
const inputAmountParam = params.get(RampUrlParamsKeys.INPUT_AMOUNT);
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 +211,7 @@ export const useRampUrlParams = (): RampUrlParams => {
callbackUrl: callbackUrlParam || undefined,
countryCode: countryCodeParam || undefined,
cryptoLocked,
+ evmTokensLoaded,
externalSessionId: externalSessionIdParam || undefined,
fiat,
inputAmount: inputAmountParam || undefined,
@@ -213,7 +223,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 +422,10 @@ export const useSetRampUrlParams = () => {
handleFiatToken,
moneriumCode
]);
+
+ useEffect(() => {
+ if (cryptoLocked) {
+ setOnChainToken(cryptoLocked);
+ }
+ }, [cryptoLocked, setOnChainToken]);
};
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..a220c7401 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) {
+ console.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) {
+ console.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..b6fa5bac4 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