From 264b33e485eac1fb20ba45899ebfe75d8f14d90c Mon Sep 17 00:00:00 2001 From: Kacper Szarkiewicz Date: Thu, 12 Feb 2026 11:48:28 +0100 Subject: [PATCH 1/5] wip --- apps/frontend/src/hooks/useRampUrlParams.ts | 35 ++++++++++++++++----- apps/frontend/src/main.tsx | 5 ++- apps/frontend/src/stores/evmTokensStore.ts | 13 ++++++++ apps/frontend/src/types/searchParams.ts | 10 ++++-- 4 files changed, 53 insertions(+), 10 deletions(-) create mode 100644 apps/frontend/src/stores/evmTokensStore.ts diff --git a/apps/frontend/src/hooks/useRampUrlParams.ts b/apps/frontend/src/hooks/useRampUrlParams.ts index 8cd96bb1b..664481472 100644 --- a/apps/frontend/src/hooks/useRampUrlParams.ts +++ b/apps/frontend/src/hooks/useRampUrlParams.ts @@ -2,8 +2,11 @@ import { AssetHubToken, DestinationType, EPaymentMethod, + type EvmNetworks, EvmToken, FiatToken, + getEvmTokenConfig, + isNetworkEVM, Networks, OnChainToken, PaymentMethod, @@ -17,6 +20,7 @@ import { useNetwork } from "../contexts/network"; import { useRampActor } from "../contexts/rampState"; import { DEFAULT_RAMP_DIRECTION } from "../helpers/path"; import { QuoteService } from "../services/api"; +import { useEvmTokensLoaded } from "../stores/evmTokensStore"; import { useSetApiKey, useSetPartnerId } from "../stores/partnerStore"; import { useQuoteFormStoreActions } from "../stores/quote/useQuoteFormStore"; import { useQuoteStore } from "../stores/quote/useQuoteStore"; @@ -77,14 +81,22 @@ function findOnChainToken(tokenStr?: string, networkType?: Networks | string): O return tokenValue as unknown as OnChainToken; } else { const evmTokenEntries = Object.entries(EvmToken); - const matchedToken = evmTokenEntries.find(([_, token]) => token.toUpperCase() === tokenStr); + const matchedStaticToken = evmTokenEntries.find(([_, token]) => token.toUpperCase() === tokenStr); - if (!matchedToken) { - return EvmToken.USDC; + if (matchedStaticToken) { + const [_, tokenValue] = matchedStaticToken; + return tokenValue as OnChainToken; } - const [_, tokenValue] = matchedToken; - return tokenValue as OnChainToken; + if (isNetworkEVM(networkType as Networks)) { + const dynamicConfig = getEvmTokenConfig(); + const networkTokens = dynamicConfig[networkType as EvmNetworks]; + if (networkTokens && tokenStr in networkTokens) { + return tokenStr as OnChainToken; + } + } + + return EvmToken.USDC; } } @@ -171,6 +183,7 @@ export const useRampUrlParams = (): RampUrlParams => { const params = useMemo(() => new URLSearchParams(window.location.search), []); const { selectedNetwork } = useNetwork(); const rampDirectionStore = useRampDirection(); + const evmTokensLoaded = useEvmTokensLoaded(); const urlParams = useMemo(() => { const rampDirectionParam = params.get(RampUrlParamsKeys.RAMP_TYPE)?.toUpperCase(); @@ -182,7 +195,7 @@ export const useRampUrlParams = (): RampUrlParams => { 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 paymentMethodParam = params.get(RampUrlParamsKeys.PAYMENT_METHOD)?.toLowerCase() 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); @@ -202,6 +215,7 @@ export const useRampUrlParams = (): RampUrlParams => { callbackUrl: callbackUrlParam || undefined, countryCode: countryCodeParam || undefined, cryptoLocked, + evmTokensLoaded, externalSessionId: externalSessionIdParam || undefined, fiat, inputAmount: inputAmountParam || undefined, @@ -213,7 +227,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 +426,10 @@ export const useSetRampUrlParams = () => { handleFiatToken, moneriumCode ]); + + useEffect(() => { + if (cryptoLocked) { + setOnChainToken(cryptoLocked); + } + }, [cryptoLocked, setOnChainToken]); }; diff --git a/apps/frontend/src/main.tsx b/apps/frontend/src/main.tsx index 2d85ef6b8..71a5af622 100644 --- a/apps/frontend/src/main.tsx +++ b/apps/frontend/src/main.tsx @@ -19,6 +19,7 @@ import { NetworkProvider } from "./contexts/network"; import { PolkadotNodeProvider } from "./contexts/polkadotNode"; import { PolkadotWalletStateProvider } from "./contexts/polkadotWallet"; import { initializeEvmTokens } from "./services/tokens"; +import { useEvmTokensStore } from "./stores/evmTokensStore"; import { wagmiConfig } from "./wagmiConfig"; import "./helpers/googleTranslate"; import { PersistentRampStateProvider } from "./contexts/rampState"; @@ -77,7 +78,9 @@ if (!root) { } // Initialize dynamic EVM tokens from SquidRouter API (falls back to static config on failure) -initializeEvmTokens(); +initializeEvmTokens().then(() => { + useEvmTokensStore.getState().setLoaded(); +}); createRoot(root).render( diff --git a/apps/frontend/src/stores/evmTokensStore.ts b/apps/frontend/src/stores/evmTokensStore.ts new file mode 100644 index 000000000..03dfafd58 --- /dev/null +++ b/apps/frontend/src/stores/evmTokensStore.ts @@ -0,0 +1,13 @@ +import { create } from "zustand"; + +interface EvmTokensState { + isLoaded: boolean; + setLoaded: () => void; +} + +export const useEvmTokensStore = create(set => ({ + isLoaded: false, + setLoaded: () => set({ isLoaded: true }) +})); + +export const useEvmTokensLoaded = () => useEvmTokensStore(state => state.isLoaded); diff --git a/apps/frontend/src/types/searchParams.ts b/apps/frontend/src/types/searchParams.ts index 15fc59cb5..9d91ffde5 100644 --- a/apps/frontend/src/types/searchParams.ts +++ b/apps/frontend/src/types/searchParams.ts @@ -22,9 +22,15 @@ export const rampSearchSchema = z.object({ inputAmount: stringOrNumberParam, network: z.string().optional(), partnerId: z.string().optional(), - paymentMethod: z.nativeEnum(EPaymentMethod).optional().catch(undefined), + paymentMethod: z + .preprocess(val => (typeof val === "string" ? val.toLowerCase() : val), z.nativeEnum(EPaymentMethod)) + .optional() + .catch(undefined), quoteId: z.string().optional(), - rampType: z.nativeEnum(RampDirection).optional().catch(undefined), + rampType: z + .preprocess(val => (typeof val === "string" ? val.toUpperCase() : val), z.nativeEnum(RampDirection)) + .optional() + .catch(undefined), walletAddressLocked: z.string().optional() }); From ebdd42724890305a67404beb4a5499729ca23934 Mon Sep 17 00:00:00 2001 From: Kacper Szarkiewicz Date: Thu, 12 Feb 2026 14:28:19 +0100 Subject: [PATCH 2/5] Use React's useSyncExternalStore to subscribe directly to shared module state --- apps/frontend/src/hooks/useRampUrlParams.ts | 23 +++++++++++-------- apps/frontend/src/main.tsx | 5 +--- apps/frontend/src/stores/evmTokensStore.ts | 13 ----------- apps/frontend/src/types/searchParams.ts | 11 ++------- .../shared/src/tokens/evm/dynamicEvmTokens.ts | 15 ++++++++++++ 5 files changed, 31 insertions(+), 36 deletions(-) delete mode 100644 apps/frontend/src/stores/evmTokensStore.ts diff --git a/apps/frontend/src/hooks/useRampUrlParams.ts b/apps/frontend/src/hooks/useRampUrlParams.ts index 664481472..675577d99 100644 --- a/apps/frontend/src/hooks/useRampUrlParams.ts +++ b/apps/frontend/src/hooks/useRampUrlParams.ts @@ -6,21 +6,22 @@ import { EvmToken, FiatToken, getEvmTokenConfig, + getEvmTokensLoadedSnapshot, isNetworkEVM, Networks, OnChainToken, 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"; import { DEFAULT_RAMP_DIRECTION } from "../helpers/path"; import { QuoteService } from "../services/api"; -import { useEvmTokensLoaded } from "../stores/evmTokensStore"; import { useSetApiKey, useSetPartnerId } from "../stores/partnerStore"; import { useQuoteFormStoreActions } from "../stores/quote/useQuoteFormStore"; import { useQuoteStore } from "../stores/quote/useQuoteStore"; @@ -183,23 +184,25 @@ export const useRampUrlParams = (): RampUrlParams => { const params = useMemo(() => new URLSearchParams(window.location.search), []); const { selectedNetwork } = useNetwork(); const rampDirectionStore = useRampDirection(); - const evmTokensLoaded = useEvmTokensLoaded(); + 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)?.toLowerCase() 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 diff --git a/apps/frontend/src/main.tsx b/apps/frontend/src/main.tsx index 71a5af622..2d85ef6b8 100644 --- a/apps/frontend/src/main.tsx +++ b/apps/frontend/src/main.tsx @@ -19,7 +19,6 @@ import { NetworkProvider } from "./contexts/network"; import { PolkadotNodeProvider } from "./contexts/polkadotNode"; import { PolkadotWalletStateProvider } from "./contexts/polkadotWallet"; import { initializeEvmTokens } from "./services/tokens"; -import { useEvmTokensStore } from "./stores/evmTokensStore"; import { wagmiConfig } from "./wagmiConfig"; import "./helpers/googleTranslate"; import { PersistentRampStateProvider } from "./contexts/rampState"; @@ -78,9 +77,7 @@ if (!root) { } // Initialize dynamic EVM tokens from SquidRouter API (falls back to static config on failure) -initializeEvmTokens().then(() => { - useEvmTokensStore.getState().setLoaded(); -}); +initializeEvmTokens(); createRoot(root).render( diff --git a/apps/frontend/src/stores/evmTokensStore.ts b/apps/frontend/src/stores/evmTokensStore.ts deleted file mode 100644 index 03dfafd58..000000000 --- a/apps/frontend/src/stores/evmTokensStore.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { create } from "zustand"; - -interface EvmTokensState { - isLoaded: boolean; - setLoaded: () => void; -} - -export const useEvmTokensStore = create(set => ({ - isLoaded: false, - setLoaded: () => set({ isLoaded: true }) -})); - -export const useEvmTokensLoaded = () => useEvmTokensStore(state => state.isLoaded); diff --git a/apps/frontend/src/types/searchParams.ts b/apps/frontend/src/types/searchParams.ts index 9d91ffde5..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,15 +21,9 @@ export const rampSearchSchema = z.object({ inputAmount: stringOrNumberParam, network: z.string().optional(), partnerId: z.string().optional(), - paymentMethod: z - .preprocess(val => (typeof val === "string" ? val.toLowerCase() : val), z.nativeEnum(EPaymentMethod)) - .optional() - .catch(undefined), + paymentMethod: z.string().optional(), quoteId: z.string().optional(), - rampType: z - .preprocess(val => (typeof val === "string" ? val.toUpperCase() : val), 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..f92c92385 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,12 +296,14 @@ export async function initializeEvmTokens(): Promise { state.tokensByNetwork = mergeWithStaticConfig(groupedTokens); state.priceBySymbol = buildPriceLookup(state.tokensByNetwork); state.isLoaded = true; + for (const listener of evmTokenListeners) listener(); } 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) listener(); } } From ba6ee2fe76046784e5545f3c65f226050c47d612 Mon Sep 17 00:00:00 2001 From: Kacper Szarkiewicz Date: Thu, 12 Feb 2026 15:48:18 +0100 Subject: [PATCH 3/5] simplify findOnChainToken logic --- apps/frontend/src/hooks/useRampUrlParams.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/apps/frontend/src/hooks/useRampUrlParams.ts b/apps/frontend/src/hooks/useRampUrlParams.ts index 675577d99..bd1803a4d 100644 --- a/apps/frontend/src/hooks/useRampUrlParams.ts +++ b/apps/frontend/src/hooks/useRampUrlParams.ts @@ -81,14 +81,6 @@ function findOnChainToken(tokenStr?: string, networkType?: Networks | string): O const [_, tokenValue] = matchedToken; return tokenValue as unknown as OnChainToken; } else { - const evmTokenEntries = Object.entries(EvmToken); - const matchedStaticToken = evmTokenEntries.find(([_, token]) => token.toUpperCase() === tokenStr); - - if (matchedStaticToken) { - const [_, tokenValue] = matchedStaticToken; - return tokenValue as OnChainToken; - } - if (isNetworkEVM(networkType as Networks)) { const dynamicConfig = getEvmTokenConfig(); const networkTokens = dynamicConfig[networkType as EvmNetworks]; From 446b2e8eae2aa1231aa3adee678086939127b4ea Mon Sep 17 00:00:00 2001 From: Kacper Szarkiewicz Date: Fri, 13 Feb 2026 17:41:10 +0100 Subject: [PATCH 4/5] fixes --- apps/frontend/App.css | 6 +++++- .../src/components/menus/SettingsMenu/index.tsx | 10 +++++----- apps/frontend/src/hooks/quote/schema.ts | 4 ++-- apps/frontend/src/hooks/quote/useQuoteService.ts | 4 ++-- apps/frontend/src/hooks/useRampUrlParams.ts | 13 +++++++------ .../src/sections/business/WhyVortexWidget/index.tsx | 12 +++++++----- apps/frontend/src/services/api/quote.service.ts | 11 ++++++----- apps/frontend/src/services/signingService.tsx | 8 -------- apps/frontend/src/stores/quote/useQuoteFormStore.ts | 7 ++++--- apps/frontend/src/stores/quote/useQuoteStore.ts | 7 ++++--- apps/frontend/src/types/phases.ts | 4 ++-- packages/shared/src/tokens/types/base.ts | 2 ++ packages/shared/src/tokens/utils/helpers.ts | 6 +++--- 13 files changed, 49 insertions(+), 45 deletions(-) diff --git a/apps/frontend/App.css b/apps/frontend/App.css index fea3b0ed5..fa8bd111d 100644 --- a/apps/frontend/App.css +++ b/apps/frontend/App.css @@ -125,7 +125,11 @@ } .btn-vortex-primary { - @apply bg-blue-700 text-white rounded-[var(--radius-field)] border border-blue-700 cursor-pointer; + @apply bg-blue-700 text-white rounded-[var(--radius-field)] border border-blue-700 cursor-pointer transition-transform; +} + +.btn-vortex-primary:active { + transform: scale(0.98); } .btn-vortex-primary:hover { diff --git a/apps/frontend/src/components/menus/SettingsMenu/index.tsx b/apps/frontend/src/components/menus/SettingsMenu/index.tsx index 294f03719..7eb07b023 100644 --- a/apps/frontend/src/components/menus/SettingsMenu/index.tsx +++ b/apps/frontend/src/components/menus/SettingsMenu/index.tsx @@ -75,13 +75,13 @@ export const SettingsMenu = () => {
{isAuthenticated && userEmail && ( <> -
-
+
+
- {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 bd1803a4d..a2993d04a 100644 --- a/apps/frontend/src/hooks/useRampUrlParams.ts +++ b/apps/frontend/src/hooks/useRampUrlParams.ts @@ -10,6 +10,7 @@ import { isNetworkEVM, Networks, OnChainToken, + OnChainTokenSymbol, PaymentMethod, QuoteResponse, RampDirection, @@ -38,7 +39,7 @@ interface RampUrlParams { moneriumCode?: string; fiat?: FiatToken; countryCode?: string; - cryptoLocked?: OnChainToken; + cryptoLocked?: OnChainTokenSymbol; paymentMethod?: PaymentMethod; walletLocked?: string; callbackUrl?: string; @@ -63,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; } @@ -85,7 +86,7 @@ function findOnChainToken(tokenStr?: string, networkType?: Networks | string): O const dynamicConfig = getEvmTokenConfig(); const networkTokens = dynamicConfig[networkType as EvmNetworks]; if (networkTokens && tokenStr in networkTokens) { - return tokenStr as OnChainToken; + return tokenStr; } } @@ -113,7 +114,7 @@ const mapFiatToDestination = (fiatToken: FiatToken): DestinationType => { interface QuoteParams { inputAmount?: Big; - onChainToken: OnChainToken; + onChainToken: OnChainTokenSymbol; fiatToken: FiatToken; selectedNetwork: DestinationType; rampType: RampDirection; @@ -124,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 => { 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 = () => {
-
- {t("pages.business.hero.comingSoon")} -
- +
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/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 From f908533d605d8f80b0e693232334855659b27780 Mon Sep 17 00:00:00 2001 From: Kacper Szarkiewicz Date: Fri, 13 Feb 2026 17:59:22 +0100 Subject: [PATCH 5/5] update listener error catch --- .../shared/src/tokens/evm/dynamicEvmTokens.ts | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/packages/shared/src/tokens/evm/dynamicEvmTokens.ts b/packages/shared/src/tokens/evm/dynamicEvmTokens.ts index f92c92385..a220c7401 100644 --- a/packages/shared/src/tokens/evm/dynamicEvmTokens.ts +++ b/packages/shared/src/tokens/evm/dynamicEvmTokens.ts @@ -296,14 +296,25 @@ export async function initializeEvmTokens(): Promise { state.tokensByNetwork = mergeWithStaticConfig(groupedTokens); state.priceBySymbol = buildPriceLookup(state.tokensByNetwork); state.isLoaded = true; - for (const listener of evmTokenListeners) listener(); + 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) listener(); + } + for (const listener of evmTokenListeners) { + try { + listener(); + } catch (listenerErr) { + console.error("[DynamicEvmTokens] Error in EVM token listener", listenerErr); + } } }