From ddf35b1706d64004f684fdca251b3c3c8270adcc Mon Sep 17 00:00:00 2001 From: Kacper Szarkiewicz Date: Thu, 5 Feb 2026 11:39:01 +0100 Subject: [PATCH 01/19] Remove unused networkAssetIcon from token types, configs, and consumers --- .../src/components/Ramp/Offramp/index.tsx | 2 +- .../src/components/Ramp/Onramp/index.tsx | 4 +- .../TokenSelectionList/helpers.tsx | 4 +- .../shared/src/services/squidrouter/config.ts | 3 +- packages/shared/src/tokens/assethub/config.ts | 3 - packages/shared/src/tokens/evm/config.ts | 84 +++++++------------ .../shared/src/tokens/evm/dynamicEvmTokens.ts | 29 +++---- packages/shared/src/tokens/types/assethub.ts | 1 - packages/shared/src/tokens/types/evm.ts | 3 +- 9 files changed, 50 insertions(+), 83 deletions(-) diff --git a/apps/frontend/src/components/Ramp/Offramp/index.tsx b/apps/frontend/src/components/Ramp/Offramp/index.tsx index b76a383d5..178235bc0 100644 --- a/apps/frontend/src/components/Ramp/Offramp/index.tsx +++ b/apps/frontend/src/components/Ramp/Offramp/index.tsx @@ -81,7 +81,7 @@ export const Offramp = () => { () => ( <> { const ReceiveNumericInput = useMemo( () => ( { tokenSymbol={toToken.assetSymbol} /> ), - [toToken.networkAssetIcon, toToken.assetSymbol, form, quoteLoading, toAmount, openTokenSelectModal, toIconInfo] + [toToken.assetSymbol, form, quoteLoading, toAmount, openTokenSelectModal, toIconInfo] ); const handleConfirm = useCallback(() => { diff --git a/apps/frontend/src/components/TokenSelection/TokenSelectionList/helpers.tsx b/apps/frontend/src/components/TokenSelection/TokenSelectionList/helpers.tsx index 27ae49229..27b47867c 100644 --- a/apps/frontend/src/components/TokenSelection/TokenSelectionList/helpers.tsx +++ b/apps/frontend/src/components/TokenSelection/TokenSelectionList/helpers.tsx @@ -63,7 +63,7 @@ export function useTokenDefinitions(filter: string, selectedNetworkFilter: Netwo function getOnChainTokensDefinitionsForNetwork(selectedNetwork: Networks): ExtendedTokenDefinition[] { if (selectedNetwork === Networks.AssetHub) { return Object.entries(assetHubTokenConfig).map(([key, value]) => ({ - assetIcon: value.networkAssetIcon, + assetIcon: value.assetSymbol, assetSymbol: value.assetSymbol, details: value as OnChainTokenDetails, logoURI: value.logoURI, @@ -75,7 +75,7 @@ function getOnChainTokensDefinitionsForNetwork(selectedNetwork: Networks): Exten const evmConfig = getEvmTokenConfig(); const networkConfig = evmConfig[selectedNetwork as EvmNetworks] ?? {}; return Object.entries(networkConfig).map(([key, value]) => ({ - assetIcon: value?.logoURI ?? value?.networkAssetIcon ?? "", + assetIcon: value?.assetSymbol ?? key, assetSymbol: value?.assetSymbol ?? key, details: value as OnChainTokenDetails, fallbackLogoURI: value?.fallbackLogoURI, diff --git a/packages/shared/src/services/squidrouter/config.ts b/packages/shared/src/services/squidrouter/config.ts index 233832b98..91a1d0b2d 100644 --- a/packages/shared/src/services/squidrouter/config.ts +++ b/packages/shared/src/services/squidrouter/config.ts @@ -1,4 +1,5 @@ -import { AXL_USDC_MOONBEAM, getNetworkId, Networks } from "../../index"; +import { getNetworkId, Networks } from "../../helpers/networks"; +import { AXL_USDC_MOONBEAM } from "../../tokens/moonbeam/config"; export const SQUIDROUTER_FEE_OVERPAY = 0.25; // 25% overpayment export const MOONBEAM_SQUIDROUTER_SWAP_MIN_VALUE_RAW = "10000000000000000"; // 0.01 GLMR in raw units diff --git a/packages/shared/src/tokens/assethub/config.ts b/packages/shared/src/tokens/assethub/config.ts index 83881d28a..e4f3c4ce4 100644 --- a/packages/shared/src/tokens/assethub/config.ts +++ b/packages/shared/src/tokens/assethub/config.ts @@ -16,7 +16,6 @@ export const assetHubTokenConfig: Record = isNative: false, logoURI: "https://raw.githubusercontent.com/0xsquid/assets/main/images/tokens/usdc.svg", network: Networks.AssetHub, - networkAssetIcon: "assethubUSDC", pendulumRepresentative: PENDULUM_USDC_ASSETHUB, type: TokenType.AssetHub }, @@ -28,7 +27,6 @@ export const assetHubTokenConfig: Record = isNative: false, logoURI: "https://raw.githubusercontent.com/0xsquid/assets/main/images/tokens/usdt.svg", network: Networks.AssetHub, - networkAssetIcon: "assethubUSDT", pendulumRepresentative: PENDULUM_USDC_ASSETHUB, // This is because USDC is used by Nabla type: TokenType.AssetHub }, @@ -39,7 +37,6 @@ export const assetHubTokenConfig: Record = isNative: true, logoURI: "https://raw.githubusercontent.com/0xsquid/assets/main/images/tokens/dot.svg", network: Networks.AssetHub, - networkAssetIcon: "assethubDOT", pendulumRepresentative: PENDULUM_USDC_ASSETHUB, // This is because USDC is used by Nabla type: TokenType.AssetHub } diff --git a/packages/shared/src/tokens/evm/config.ts b/packages/shared/src/tokens/evm/config.ts index 02af40f5d..92e0e5826 100644 --- a/packages/shared/src/tokens/evm/config.ts +++ b/packages/shared/src/tokens/evm/config.ts @@ -11,41 +11,37 @@ export const evmTokenConfig: Record 0 ? (networkEntries[0][1] as Networks) : null; } -function getNetworkAssetIcon(network: Networks, symbol: string): string { - const networkName = network.toLowerCase(); - const cleanSymbol = symbol.replace(/[^a-zA-Z0-9]/g, "").toLowerCase(); - return `${networkName}${cleanSymbol}`; -} - -function generateFallbackLogoURI(chainId: number, address: string): string { - return `https://raw.githubusercontent.com/0xsquid/assets/main/images/migration/webp/${chainId}_${address.toLowerCase()}.webp`; -} - function shouldIncludeToken(token: SquidRouterToken): boolean { const symbol = token.symbol.toUpperCase(); @@ -106,6 +96,10 @@ function shouldIncludeToken(token: SquidRouterToken): boolean { return true; } +function generateFallbackLogoURI(chainId: number, address: string): string { + return `https://raw.githubusercontent.com/0xsquid/assets/main/images/migration/webp/${chainId}_${address.toLowerCase()}.webp`; +} + function mapSquidTokenToEvmTokenDetails(token: SquidRouterToken): EvmTokenDetails | null { const network = getNetworkFromChainId(token.chainId); if (!network || !isNetworkEVM(network)) { @@ -124,11 +118,10 @@ function mapSquidTokenToEvmTokenDetails(token: SquidRouterToken): EvmTokenDetail assetSymbol: token.symbol, decimals: token.decimals, erc20AddressSourceChain: erc20Address, - fallbackLogoURI: generateFallbackLogoURI(parseInt(token.chainId, 10), erc20Address), + fallbackLogoURI: generateFallbackLogoURI(parseInt(token.chainId, 10), token.address), isNative, logoURI: token.logoURI, network, - networkAssetIcon: getNetworkAssetIcon(network, token.symbol), pendulumRepresentative: PENDULUM_USDC_AXL, type: TokenType.Evm, usdPrice: token.usdPrice @@ -180,16 +173,20 @@ function mergeWithStaticConfig( ); } - // Static token exists and dynamic token exists - merge, static takes priority + // Static token exists and dynamic token exists - merge, static takes priority, mark as static merged[network][normalizedSymbol] = { ...staticToken, - fallbackLogoURI: staticToken.fallbackLogoURI ?? dynamicToken.fallbackLogoURI, + fallbackLogoURI: dynamicToken.fallbackLogoURI ?? staticToken.fallbackLogoURI, + isFromStaticConfig: true, logoURI: staticToken.logoURI ?? dynamicToken.logoURI, usdPrice: dynamicToken.usdPrice ?? staticToken.usdPrice }; } else { - // Static token exists but no dynamic token - use static as-is - merged[network][normalizedSymbol] = staticToken; + // Static token exists but no dynamic token - use static as-is, mark as static + merged[network][normalizedSymbol] = { + ...staticToken, + isFromStaticConfig: true + }; } } }); diff --git a/packages/shared/src/tokens/types/assethub.ts b/packages/shared/src/tokens/types/assethub.ts index e66386c59..b1eae442a 100644 --- a/packages/shared/src/tokens/types/assethub.ts +++ b/packages/shared/src/tokens/types/assethub.ts @@ -9,7 +9,6 @@ import { BaseTokenDetails, TokenType } from "./base"; export interface AssetHubTokenDetails extends BaseTokenDetails { type: TokenType.AssetHub; assetSymbol: string; - networkAssetIcon: string; logoURI: string; network: Networks; foreignAssetId?: number; // The identifier of this token in AssetHub's assets registry diff --git a/packages/shared/src/tokens/types/evm.ts b/packages/shared/src/tokens/types/evm.ts index 7dea695f5..cc7168b58 100644 --- a/packages/shared/src/tokens/types/evm.ts +++ b/packages/shared/src/tokens/types/evm.ts @@ -23,7 +23,6 @@ export enum UsdLikeEvmToken { export interface EvmTokenDetails extends BaseTokenDetails { type: TokenType.Evm; assetSymbol: string; - networkAssetIcon: string; network: Networks; erc20AddressSourceChain: EvmAddress; isNative: boolean; @@ -35,6 +34,8 @@ export interface EvmTokenDetails extends BaseTokenDetails { /// Fallback URL for the token's logo image (constructed from chainId and address) fallbackLogoURI?: string; usdPrice?: number; + /// True for tokens defined in the static evmTokenConfig (used for sorting priority) + isFromStaticConfig?: boolean; } export interface EvmTokenDetailsWithBalance extends EvmTokenDetails { From 4b205e1501cde8408aa058374550f5c8d4b9ff14 Mon Sep 17 00:00:00 2001 From: Kacper Szarkiewicz Date: Thu, 5 Feb 2026 11:41:43 +0100 Subject: [PATCH 02/19] Add isFromStaticConfig flag for token sorting priority --- .../components/SelectionTokenList.tsx | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/apps/frontend/src/components/TokenSelection/TokenSelectionList/components/SelectionTokenList.tsx b/apps/frontend/src/components/TokenSelection/TokenSelectionList/components/SelectionTokenList.tsx index a35090db1..d3a13845e 100644 --- a/apps/frontend/src/components/TokenSelection/TokenSelectionList/components/SelectionTokenList.tsx +++ b/apps/frontend/src/components/TokenSelection/TokenSelectionList/components/SelectionTokenList.tsx @@ -1,4 +1,5 @@ import { useVirtualizer } from "@tanstack/react-virtual"; +import { isNetworkEVM } from "@vortexfi/shared"; import { useMemo, useRef } from "react"; import { useNetwork } from "../../../../contexts/network"; import { cn } from "../../../../helpers/cn"; @@ -16,7 +17,20 @@ function getBalanceKey(network: string, symbol: string): string { return `${network}-${symbol}`; } -function sortByBalance( +/** + * Sorts the given token definitions for optimal UX in the token selection list. + * + * Sort priority: + * 1. Tokens with higher USD balance come first. + * 2. For equal USD value, tokens with higher raw balance come first. + * 3. Tokens from "static config" (see isFromStaticConfig) or non-EVM networks come before dynamic/discovered tokens. + * 4. Fallback to asset symbol alphabetical sort. + * + * @param definitions - Array of tokens to display in the modal. + * @param balances - Map keyed by 'network-symbol', containing balance and balanceUsd for each token. + * @returns Sorted array of ExtendedTokenDefinition. + */ +function sortTokens( definitions: ExtendedTokenDefinition[], balances: Map ): ExtendedTokenDefinition[] { @@ -30,7 +44,6 @@ function sortByBalance( return usdB - usdA; } - // When USD values are equal (e.g., both 0), sort by raw balance const rawBalanceA = parseFloat(balanceA?.balance ?? "0"); const rawBalanceB = parseFloat(balanceB?.balance ?? "0"); @@ -38,6 +51,12 @@ function sortByBalance( return rawBalanceB - rawBalanceA; } + const isStaticA = !isNetworkEVM(a.network) || (a.details as { isFromStaticConfig?: boolean }).isFromStaticConfig; + const isStaticB = !isNetworkEVM(b.network) || (b.details as { isFromStaticConfig?: boolean }).isFromStaticConfig; + if (isStaticA !== isStaticB) { + return isStaticA ? -1 : 1; + } + return a.assetSymbol.localeCompare(b.assetSymbol); }); } @@ -54,7 +73,7 @@ export const SelectionTokenList = () => { const balances = useTokenBalances(); const currentDefinitions = useMemo( - () => (isFiatDirection ? filteredDefinitions : sortByBalance(filteredDefinitions, balances)), + () => (isFiatDirection ? filteredDefinitions : sortTokens(filteredDefinitions, balances)), [isFiatDirection, filteredDefinitions, balances] ); From 0039c91410959045103194eb008d684d97eca9a2 Mon Sep 17 00:00:00 2001 From: Kacper Szarkiewicz Date: Thu, 5 Feb 2026 11:46:59 +0100 Subject: [PATCH 03/19] Fix token icon fallback chain: prefer dynamic fallbackLogoURI and avoid loading flash --- .../src/components/CurrencyExchange/index.tsx | 7 ++++--- .../src/components/TokenIcon/index.tsx | 20 +++++++++---------- .../components/buttons/AssetButton/index.tsx | 2 +- apps/frontend/src/hooks/useTokenIcon.ts | 11 +++------- packages/shared/src/tokens/moonbeam/config.ts | 1 - 5 files changed, 18 insertions(+), 23 deletions(-) diff --git a/apps/frontend/src/components/CurrencyExchange/index.tsx b/apps/frontend/src/components/CurrencyExchange/index.tsx index 37ba70cf5..a32583df1 100644 --- a/apps/frontend/src/components/CurrencyExchange/index.tsx +++ b/apps/frontend/src/components/CurrencyExchange/index.tsx @@ -33,15 +33,16 @@ export const CurrencyExchange = ({ outputNetwork, inputIcon: inputIconProp, outputIcon: outputIconProp, - inputFallbackIcon, - outputFallbackIcon + inputFallbackIcon: inputFallbackIconProp, + outputFallbackIcon: outputFallbackIconProp }: CurrencyExchangeProps) => { - // Use useTokenIcon for fallback icons when explicit icon props aren't provided const inputIconFallback = useTokenIcon(inputCurrency, inputNetwork); const outputIconFallback = useTokenIcon(outputCurrency, outputNetwork); const inputIcon = inputIconProp ?? inputIconFallback.iconSrc; const outputIcon = outputIconProp ?? outputIconFallback.iconSrc; + const inputFallbackIcon = inputFallbackIconProp ?? inputIconFallback.fallbackIconSrc; + const outputFallbackIcon = outputFallbackIconProp ?? outputIconFallback.fallbackIconSrc; if (layout === "vertical") { return ( diff --git a/apps/frontend/src/components/TokenIcon/index.tsx b/apps/frontend/src/components/TokenIcon/index.tsx index e21211417..d5bf68f4f 100644 --- a/apps/frontend/src/components/TokenIcon/index.tsx +++ b/apps/frontend/src/components/TokenIcon/index.tsx @@ -14,20 +14,14 @@ export const TokenIcon: FC = memo(function TokenIcon({ src, fall const [imgError, setImgError] = useState(false); const [fallbackError, setFallbackError] = useState(false); - const getImageSrc = () => { - if (!imgError) return src; - if (fallbackSrc && !fallbackError) return fallbackSrc; - return placeholderIcon; - }; - const handleError = () => { if (!imgError) { setImgError(true); - setIsLoading(true); - } else if (fallbackSrc && !fallbackError) { - setFallbackError(true); - setIsLoading(true); + if (!fallbackSrc) { + setIsLoading(false); + } } else { + setFallbackError(true); setIsLoading(false); } }; @@ -36,6 +30,12 @@ export const TokenIcon: FC = memo(function TokenIcon({ src, fall setIsLoading(false); }; + const getImageSrc = () => { + if (!imgError) return src; + if (fallbackSrc && !fallbackError) return fallbackSrc; + return placeholderIcon; + }; + return (
{isLoading &&
} diff --git a/apps/frontend/src/components/buttons/AssetButton/index.tsx b/apps/frontend/src/components/buttons/AssetButton/index.tsx index cfae8fef8..bf11dd83e 100644 --- a/apps/frontend/src/components/buttons/AssetButton/index.tsx +++ b/apps/frontend/src/components/buttons/AssetButton/index.tsx @@ -38,7 +38,7 @@ export function AssetButton({ > Date: Fri, 6 Feb 2026 11:53:08 +0100 Subject: [PATCH 04/19] wip --- apps/api/src/api/services/hydration/swap.ts | 4 +++- packages/shared/src/tokens/evm/config.ts | 9 --------- packages/shared/src/tokens/utils/helpers.ts | 10 +++++++++- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/apps/api/src/api/services/hydration/swap.ts b/apps/api/src/api/services/hydration/swap.ts index b39e239d8..f24478318 100644 --- a/apps/api/src/api/services/hydration/swap.ts +++ b/apps/api/src/api/services/hydration/swap.ts @@ -18,7 +18,9 @@ export class HydrationRouter { const apiManager = ApiManager.getInstance(); this.cachedXcmFees = {}; this.sdk = apiManager.getApi("hydration").then(async ({ api }) => { - return createSdkContext(api, { router: { includeOnly: [PoolType.Omni, PoolType.Stable] } }); + return createSdkContext(api, { + router: { includeOnly: [PoolType.XYK] } + }); }); // Refresh transaction fees every hour diff --git a/packages/shared/src/tokens/evm/config.ts b/packages/shared/src/tokens/evm/config.ts index 92e0e5826..d45aec70b 100644 --- a/packages/shared/src/tokens/evm/config.ts +++ b/packages/shared/src/tokens/evm/config.ts @@ -35,15 +35,6 @@ export const evmTokenConfig: Record Date: Fri, 6 Feb 2026 15:10:46 +0100 Subject: [PATCH 05/19] Add PoolType.Aave to Hydration config to allow aDOT->DOT conversion --- apps/api/src/api/services/hydration/swap.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/api/src/api/services/hydration/swap.ts b/apps/api/src/api/services/hydration/swap.ts index f24478318..fd6cc6854 100644 --- a/apps/api/src/api/services/hydration/swap.ts +++ b/apps/api/src/api/services/hydration/swap.ts @@ -19,7 +19,7 @@ export class HydrationRouter { this.cachedXcmFees = {}; this.sdk = apiManager.getApi("hydration").then(async ({ api }) => { return createSdkContext(api, { - router: { includeOnly: [PoolType.XYK] } + router: { includeOnly: [PoolType.Omni, PoolType.Stable, PoolType.Aave] } }); }); From 4001ecc911369df92a2fbe4db31ab109738f0ac6 Mon Sep 17 00:00:00 2001 From: Kacper Szarkiewicz Date: Fri, 6 Feb 2026 15:10:59 +0100 Subject: [PATCH 06/19] update hydration sdk version --- bun.lock | 9 ++++----- package.json | 1 + 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/bun.lock b/bun.lock index a6cb24a37..93a8defec 100644 --- a/bun.lock +++ b/bun.lock @@ -4,10 +4,10 @@ "": { "name": "vortex-monorepo", "dependencies": { + "@galacticcouncil/sdk": "^10.6.2", "big.js": "^7.0.1", "husky": "^9.1.7", "lint-staged": "^16.1.0", - "react-window": "^2.2.5", }, "devDependencies": { "@biomejs/biome": "2.0.0", @@ -159,7 +159,6 @@ "react-hook-form": "^7.65.0", "react-i18next": "^15.4.1", "react-toastify": "^11.0.5", - "react-window": "^2.2.5", "stellar-sdk": "catalog:", "tailwind-merge": "^3.1.0", "tailwindcss": "^4.0.3", @@ -749,7 +748,7 @@ "@galacticcouncil/math-xyk": ["@galacticcouncil/math-xyk@1.2.0", "", {}, "sha512-a+qcctffczjZ61vbdoXddmWVNq5nKK/pgsKPwvSBl41BotkvCqwePBieS/8jPTMKu+7uYEGv6eh+sX0ZejrFzg=="], - "@galacticcouncil/sdk": ["@galacticcouncil/sdk@9.17.1", "", { "dependencies": { "@galacticcouncil/math-hsm": "^1.1.0", "@galacticcouncil/math-lbp": "^1.2.0", "@galacticcouncil/math-liquidity-mining": "^1.2.0", "@galacticcouncil/math-omnipool": "^1.3.0", "@galacticcouncil/math-stableswap": "^2.3.0", "@galacticcouncil/math-xyk": "^1.2.0", "@noble/hashes": "^1.6.1", "@thi.ng/cache": "^2.1.35", "@thi.ng/memoize": "^4.0.2", "bignumber.js": "^9.1.0", "lodash.clonedeep": "^4.5.0" }, "peerDependencies": { "@polkadot/api": "^16.1.1", "@polkadot/api-augment": "^16.1.1", "@polkadot/api-base": "^16.1.1", "@polkadot/api-derive": "^16.1.1", "@polkadot/keyring": "^13.5.1", "@polkadot/rpc-augment": "^16.1.1", "@polkadot/rpc-core": "^16.1.1", "@polkadot/rpc-provider": "^16.1.1", "@polkadot/types": "^16.1.1", "@polkadot/types-augment": "^16.1.1", "@polkadot/types-codec": "^16.1.1", "@polkadot/types-create": "^16.1.1", "@polkadot/types-known": "^16.1.1", "@polkadot/util": "^13.5.1", "@polkadot/util-crypto": "^13.5.1", "viem": "^2.23.7" } }, "sha512-hYqWyD26FPJ+E4w5hyJPhZGziHKrAvN5+qsFiqaRQB7Ml7i8ZtNLuMMLrcZVRKaF7HAEzffkejCTAhVfZH/t8Q=="], + "@galacticcouncil/sdk": ["@galacticcouncil/sdk@10.6.2", "", { "dependencies": { "@galacticcouncil/math-hsm": "^1.1.0", "@galacticcouncil/math-lbp": "^1.2.0", "@galacticcouncil/math-liquidity-mining": "^1.2.0", "@galacticcouncil/math-omnipool": "^1.3.0", "@galacticcouncil/math-stableswap": "^2.4.0", "@galacticcouncil/math-xyk": "^1.2.0", "@noble/hashes": "^1.6.1", "@thi.ng/cache": "^2.1.35", "@thi.ng/memoize": "^4.0.2", "bignumber.js": "^9.1.0", "lodash.clonedeep": "^4.5.0" }, "peerDependencies": { "@polkadot/api": "~16.4.8", "@polkadot/api-augment": "~16.4.8", "@polkadot/types": "~16.4.8", "@polkadot/util": "~13.5.6", "@polkadot/util-crypto": "~13.5.6", "viem": "^2.38.3" } }, "sha512-wL0+oXFD4WgyEKgl7EV7tJXUoIK7hGP+sAbR3RpXHlpXaHpdDLFfUvQkD71OMDnNUoNWN3TYVun1/0M4tTDsFg=="], "@gemini-wallet/core": ["@gemini-wallet/core@0.3.2", "", { "dependencies": { "@metamask/rpc-errors": "7.0.2", "eventemitter3": "5.0.1" }, "peerDependencies": { "viem": ">=2.0.0" } }, "sha512-Z4aHi3ECFf5oWYWM3F1rW83GJfB9OvhBYPTmb5q+VyK3uvzvS48lwo+jwh2eOoCRWEuT/crpb9Vwp2QaS5JqgQ=="], @@ -3439,8 +3438,6 @@ "react-toastify": ["react-toastify@11.0.5", "", { "dependencies": { "clsx": "^2.1.1" }, "peerDependencies": { "react": "^18 || ^19", "react-dom": "^18 || ^19" } }, "sha512-EpqHBGvnSTtHYhCPLxML05NLY2ZX0JURbAdNYa6BUkk+amz4wbKBQvoKQAB0ardvSarUBuY4Q4s1sluAzZwkmA=="], - "react-window": ["react-window@2.2.5", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" } }, "sha512-6viWvPSZvVuMIe9hrl4IIZoVfO/npiqOb03m4Z9w+VihmVzBbiudUrtUqDpsWdKvd/Ai31TCR25CBcFFAUm28w=="], - "readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], "readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], @@ -4657,6 +4654,8 @@ "viem/@scure/bip39": ["@scure/bip39@1.6.0", "", { "dependencies": { "@noble/hashes": "~1.8.0", "@scure/base": "~1.2.5" } }, "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A=="], + "vortex-backend/@galacticcouncil/sdk": ["@galacticcouncil/sdk@9.17.1", "", { "dependencies": { "@galacticcouncil/math-hsm": "^1.1.0", "@galacticcouncil/math-lbp": "^1.2.0", "@galacticcouncil/math-liquidity-mining": "^1.2.0", "@galacticcouncil/math-omnipool": "^1.3.0", "@galacticcouncil/math-stableswap": "^2.3.0", "@galacticcouncil/math-xyk": "^1.2.0", "@noble/hashes": "^1.6.1", "@thi.ng/cache": "^2.1.35", "@thi.ng/memoize": "^4.0.2", "bignumber.js": "^9.1.0", "lodash.clonedeep": "^4.5.0" }, "peerDependencies": { "@polkadot/api": "^16.1.1", "@polkadot/api-augment": "^16.1.1", "@polkadot/api-base": "^16.1.1", "@polkadot/api-derive": "^16.1.1", "@polkadot/keyring": "^13.5.1", "@polkadot/rpc-augment": "^16.1.1", "@polkadot/rpc-core": "^16.1.1", "@polkadot/rpc-provider": "^16.1.1", "@polkadot/types": "^16.1.1", "@polkadot/types-augment": "^16.1.1", "@polkadot/types-codec": "^16.1.1", "@polkadot/types-create": "^16.1.1", "@polkadot/types-known": "^16.1.1", "@polkadot/util": "^13.5.1", "@polkadot/util-crypto": "^13.5.1", "viem": "^2.23.7" } }, "sha512-hYqWyD26FPJ+E4w5hyJPhZGziHKrAvN5+qsFiqaRQB7Ml7i8ZtNLuMMLrcZVRKaF7HAEzffkejCTAhVfZH/t8Q=="], + "vortex-backend/@scure/bip39": ["@scure/bip39@1.6.0", "", { "dependencies": { "@noble/hashes": "~1.8.0", "@scure/base": "~1.2.5" } }, "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A=="], "wagmi/use-sync-external-store": ["use-sync-external-store@1.4.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw=="], diff --git a/package.json b/package.json index 8619a5ed5..e9da0c099 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "winston": "3.18.3" }, "dependencies": { + "@galacticcouncil/sdk": "^10.6.2", "big.js": "^7.0.1", "husky": "^9.1.7", "lint-staged": "^16.1.0" From 01fc996a87a6e9f66a48a52088066ef21ec1e255 Mon Sep 17 00:00:00 2001 From: Kacper Szarkiewicz Date: Fri, 6 Feb 2026 15:12:32 +0100 Subject: [PATCH 07/19] fix AXLUSDC Ethereum --- packages/shared/src/tokens/utils/helpers.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/shared/src/tokens/utils/helpers.ts b/packages/shared/src/tokens/utils/helpers.ts index 0316a9d9d..b8189664a 100644 --- a/packages/shared/src/tokens/utils/helpers.ts +++ b/packages/shared/src/tokens/utils/helpers.ts @@ -59,8 +59,8 @@ export function getOnChainTokenDetailsOrDefault( return maybeOnChainTokenDetails; } - // AXLUSDC doesn't exist on all networks (e.g. Ethereum). Fall back to native USDC. - if (onChainToken === EvmToken.AXLUSDC) { + // AXLUSDC doesn't exist Ethereum + if (onChainToken === EvmToken.AXLUSDC && network === Networks.Ethereum) { const usdcDetails = getOnChainTokenDetails(network, EvmToken.USDC, dynamicEvmTokenConfig); if (usdcDetails) { return usdcDetails; From f7ec6ffbd871175a799e1f253245daad236860fd Mon Sep 17 00:00:00 2001 From: Kacper Szarkiewicz Date: Fri, 6 Feb 2026 15:14:17 +0100 Subject: [PATCH 08/19] fix AXLUSDC Ethereum --- packages/shared/src/tokens/utils/helpers.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/shared/src/tokens/utils/helpers.ts b/packages/shared/src/tokens/utils/helpers.ts index b8189664a..6fd5d4a0c 100644 --- a/packages/shared/src/tokens/utils/helpers.ts +++ b/packages/shared/src/tokens/utils/helpers.ts @@ -54,11 +54,6 @@ export function getOnChainTokenDetailsOrDefault( onChainToken: OnChainToken, dynamicEvmTokenConfig?: Record>> ): OnChainTokenDetails { - const maybeOnChainTokenDetails = getOnChainTokenDetails(network, onChainToken, dynamicEvmTokenConfig); - if (maybeOnChainTokenDetails) { - return maybeOnChainTokenDetails; - } - // AXLUSDC doesn't exist Ethereum if (onChainToken === EvmToken.AXLUSDC && network === Networks.Ethereum) { const usdcDetails = getOnChainTokenDetails(network, EvmToken.USDC, dynamicEvmTokenConfig); @@ -67,6 +62,11 @@ export function getOnChainTokenDetailsOrDefault( } } + const maybeOnChainTokenDetails = getOnChainTokenDetails(network, onChainToken, dynamicEvmTokenConfig); + if (maybeOnChainTokenDetails) { + return maybeOnChainTokenDetails; + } + logger.current.error(`Invalid input token type: ${onChainToken}`); if (network === Networks.AssetHub) { const firstAvailableToken = Object.values(assetHubTokenConfig)[0]; From 881d651b0f8feeb2ad5748cff1c860babc0a1085 Mon Sep 17 00:00:00 2001 From: Kacper Szarkiewicz Date: Fri, 6 Feb 2026 16:10:51 +0100 Subject: [PATCH 09/19] fix filtering dynamicEvmTokens (USDC.axl case) --- packages/shared/src/tokens/evm/dynamicEvmTokens.ts | 6 +++--- packages/shared/src/tokens/moonbeam/config.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/shared/src/tokens/evm/dynamicEvmTokens.ts b/packages/shared/src/tokens/evm/dynamicEvmTokens.ts index abe13775d..697dce402 100644 --- a/packages/shared/src/tokens/evm/dynamicEvmTokens.ts +++ b/packages/shared/src/tokens/evm/dynamicEvmTokens.ts @@ -159,17 +159,17 @@ function mergeWithStaticConfig( const networkTokenConfig = evmTokenConfig[network]; if (!networkTokenConfig) return; - for (const [symbol, staticToken] of Object.entries(networkTokenConfig)) { + for (const staticToken of Object.values(networkTokenConfig)) { if (!staticToken) continue; - const normalizedSymbol = symbol.toUpperCase(); + const normalizedSymbol = staticToken.assetSymbol.toUpperCase(); const dynamicToken = dynamicTokens[network][normalizedSymbol]; if (dynamicToken) { // Warning if addresses point to different contracts (possible configuration drift or scam token) if (staticToken.erc20AddressSourceChain.toLowerCase() !== dynamicToken.erc20AddressSourceChain.toLowerCase()) { logger.current.warn( - `[DynamicEvmTokens] Address mismatch for ${symbol} on ${network}. Config: ${staticToken.erc20AddressSourceChain}, Dynamic: ${dynamicToken.erc20AddressSourceChain}. Using Config preference.` + `[DynamicEvmTokens] Address mismatch for ${normalizedSymbol} on ${network}. Config: ${staticToken.erc20AddressSourceChain}, Dynamic: ${dynamicToken.erc20AddressSourceChain}. Using Config preference.` ); } diff --git a/packages/shared/src/tokens/moonbeam/config.ts b/packages/shared/src/tokens/moonbeam/config.ts index f84ab3d7b..6ad9a36ed 100644 --- a/packages/shared/src/tokens/moonbeam/config.ts +++ b/packages/shared/src/tokens/moonbeam/config.ts @@ -12,7 +12,7 @@ export const AXL_USDC_MOONBEAM: `0x${string}` = "0xca01a1d0993565291051daff39089 export const MOONBEAM_XCM_FEE_GLMR = "50000000000000000"; export const AXL_USDC_MOONBEAM_DETAILS: EvmTokenDetails = { - assetSymbol: "axlUSDC", + assetSymbol: "USDC.axl", decimals: 6, erc20AddressSourceChain: AXL_USDC_MOONBEAM, isNative: false, From 7dffad12661c5fd293cd6d215f4e6ee399be6280 Mon Sep 17 00:00:00 2001 From: Marcel Ebert Date: Fri, 6 Feb 2026 17:27:21 +0000 Subject: [PATCH 10/19] Move galacticcouncil sdk definition to respective package.json --- apps/api/package.json | 2 +- bun.lock | 5 +---- package.json | 1 - 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/apps/api/package.json b/apps/api/package.json index 08cc9e9ce..2c786c074 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -2,7 +2,7 @@ "author": "Pendulum Chain", "dependencies": { "@galacticcouncil/api-augment": "^0.8.1", - "@galacticcouncil/sdk": "^9.16.0", + "@galacticcouncil/sdk": "^10.6.2", "@paraspell/sdk-pjs": "^11.8.4", "@pendulum-chain/api-solang": "catalog:", "@polkadot/api": "catalog:", diff --git a/bun.lock b/bun.lock index 93a8defec..547a1b13e 100644 --- a/bun.lock +++ b/bun.lock @@ -4,7 +4,6 @@ "": { "name": "vortex-monorepo", "dependencies": { - "@galacticcouncil/sdk": "^10.6.2", "big.js": "^7.0.1", "husky": "^9.1.7", "lint-staged": "^16.1.0", @@ -21,7 +20,7 @@ "version": "1.0.0", "dependencies": { "@galacticcouncil/api-augment": "^0.8.1", - "@galacticcouncil/sdk": "^9.16.0", + "@galacticcouncil/sdk": "^10.6.2", "@paraspell/sdk-pjs": "^11.8.4", "@pendulum-chain/api-solang": "catalog:", "@polkadot/api": "catalog:", @@ -4654,8 +4653,6 @@ "viem/@scure/bip39": ["@scure/bip39@1.6.0", "", { "dependencies": { "@noble/hashes": "~1.8.0", "@scure/base": "~1.2.5" } }, "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A=="], - "vortex-backend/@galacticcouncil/sdk": ["@galacticcouncil/sdk@9.17.1", "", { "dependencies": { "@galacticcouncil/math-hsm": "^1.1.0", "@galacticcouncil/math-lbp": "^1.2.0", "@galacticcouncil/math-liquidity-mining": "^1.2.0", "@galacticcouncil/math-omnipool": "^1.3.0", "@galacticcouncil/math-stableswap": "^2.3.0", "@galacticcouncil/math-xyk": "^1.2.0", "@noble/hashes": "^1.6.1", "@thi.ng/cache": "^2.1.35", "@thi.ng/memoize": "^4.0.2", "bignumber.js": "^9.1.0", "lodash.clonedeep": "^4.5.0" }, "peerDependencies": { "@polkadot/api": "^16.1.1", "@polkadot/api-augment": "^16.1.1", "@polkadot/api-base": "^16.1.1", "@polkadot/api-derive": "^16.1.1", "@polkadot/keyring": "^13.5.1", "@polkadot/rpc-augment": "^16.1.1", "@polkadot/rpc-core": "^16.1.1", "@polkadot/rpc-provider": "^16.1.1", "@polkadot/types": "^16.1.1", "@polkadot/types-augment": "^16.1.1", "@polkadot/types-codec": "^16.1.1", "@polkadot/types-create": "^16.1.1", "@polkadot/types-known": "^16.1.1", "@polkadot/util": "^13.5.1", "@polkadot/util-crypto": "^13.5.1", "viem": "^2.23.7" } }, "sha512-hYqWyD26FPJ+E4w5hyJPhZGziHKrAvN5+qsFiqaRQB7Ml7i8ZtNLuMMLrcZVRKaF7HAEzffkejCTAhVfZH/t8Q=="], - "vortex-backend/@scure/bip39": ["@scure/bip39@1.6.0", "", { "dependencies": { "@noble/hashes": "~1.8.0", "@scure/base": "~1.2.5" } }, "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A=="], "wagmi/use-sync-external-store": ["use-sync-external-store@1.4.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw=="], diff --git a/package.json b/package.json index e9da0c099..8619a5ed5 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,6 @@ "winston": "3.18.3" }, "dependencies": { - "@galacticcouncil/sdk": "^10.6.2", "big.js": "^7.0.1", "husky": "^9.1.7", "lint-staged": "^16.1.0" From c05b51c653a238cf0cab69361997eb7532285dd1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 6 Feb 2026 18:01:19 +0000 Subject: [PATCH 11/19] Initial plan From a3832598ce62117078738876af2518e7123c084e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 6 Feb 2026 18:06:25 +0000 Subject: [PATCH 12/19] Fix token lookup keying: preserve enum keys and add symbol aliases - Fix mergeWithStaticConfig to preserve static config enum keys (e.g., EvmToken.AXLUSDC) - Add alias entries under normalized symbols for flexible lookups - Remove special case for AXLUSDC in getOnChainTokenDetailsOrDefault - This fixes broken enum-based lookups like getOnChainTokenDetails(..., EvmToken.AXLUSDC) Co-authored-by: ebma <6690623+ebma@users.noreply.github.com> --- .../shared/src/tokens/evm/dynamicEvmTokens.ts | 24 ++++++++++++++++--- packages/shared/src/tokens/utils/helpers.ts | 8 ------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/packages/shared/src/tokens/evm/dynamicEvmTokens.ts b/packages/shared/src/tokens/evm/dynamicEvmTokens.ts index 697dce402..516569783 100644 --- a/packages/shared/src/tokens/evm/dynamicEvmTokens.ts +++ b/packages/shared/src/tokens/evm/dynamicEvmTokens.ts @@ -147,6 +147,7 @@ function groupTokensByNetwork(tokens: EvmTokenDetails[]): Record>> @@ -159,7 +160,8 @@ function mergeWithStaticConfig( const networkTokenConfig = evmTokenConfig[network]; if (!networkTokenConfig) return; - for (const staticToken of Object.values(networkTokenConfig)) { + // Iterate over entries to preserve the static config key (enum value) + for (const [staticTokenKey, staticToken] of Object.entries(networkTokenConfig)) { if (!staticToken) continue; const normalizedSymbol = staticToken.assetSymbol.toUpperCase(); @@ -174,19 +176,35 @@ function mergeWithStaticConfig( } // Static token exists and dynamic token exists - merge, static takes priority, mark as static - merged[network][normalizedSymbol] = { + const mergedToken = { ...staticToken, fallbackLogoURI: dynamicToken.fallbackLogoURI ?? staticToken.fallbackLogoURI, isFromStaticConfig: true, logoURI: staticToken.logoURI ?? dynamicToken.logoURI, usdPrice: dynamicToken.usdPrice ?? staticToken.usdPrice }; + + // Store under the static config key (enum value) for proper enum-based lookups + merged[network][staticTokenKey] = mergedToken; + + // Also store under normalized symbol if different from the key, for symbol-based lookups + if (normalizedSymbol !== staticTokenKey) { + merged[network][normalizedSymbol] = mergedToken; + } } else { // Static token exists but no dynamic token - use static as-is, mark as static - merged[network][normalizedSymbol] = { + const staticTokenWithFlag = { ...staticToken, isFromStaticConfig: true }; + + // Store under the static config key (enum value) + merged[network][staticTokenKey] = staticTokenWithFlag; + + // Also store under normalized symbol if different from the key + if (normalizedSymbol !== staticTokenKey) { + merged[network][normalizedSymbol] = staticTokenWithFlag; + } } } }); diff --git a/packages/shared/src/tokens/utils/helpers.ts b/packages/shared/src/tokens/utils/helpers.ts index 6fd5d4a0c..abd74f320 100644 --- a/packages/shared/src/tokens/utils/helpers.ts +++ b/packages/shared/src/tokens/utils/helpers.ts @@ -54,14 +54,6 @@ export function getOnChainTokenDetailsOrDefault( onChainToken: OnChainToken, dynamicEvmTokenConfig?: Record>> ): OnChainTokenDetails { - // AXLUSDC doesn't exist Ethereum - if (onChainToken === EvmToken.AXLUSDC && network === Networks.Ethereum) { - const usdcDetails = getOnChainTokenDetails(network, EvmToken.USDC, dynamicEvmTokenConfig); - if (usdcDetails) { - return usdcDetails; - } - } - const maybeOnChainTokenDetails = getOnChainTokenDetails(network, onChainToken, dynamicEvmTokenConfig); if (maybeOnChainTokenDetails) { return maybeOnChainTokenDetails; From c1364c8ebf81f5ad3af271610d0bab9b4882575d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 6 Feb 2026 18:07:10 +0000 Subject: [PATCH 13/19] Fix formatting: remove trailing whitespace Co-authored-by: ebma <6690623+ebma@users.noreply.github.com> --- packages/shared/src/tokens/evm/dynamicEvmTokens.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/shared/src/tokens/evm/dynamicEvmTokens.ts b/packages/shared/src/tokens/evm/dynamicEvmTokens.ts index 516569783..749ccdd8c 100644 --- a/packages/shared/src/tokens/evm/dynamicEvmTokens.ts +++ b/packages/shared/src/tokens/evm/dynamicEvmTokens.ts @@ -183,10 +183,10 @@ function mergeWithStaticConfig( logoURI: staticToken.logoURI ?? dynamicToken.logoURI, usdPrice: dynamicToken.usdPrice ?? staticToken.usdPrice }; - + // Store under the static config key (enum value) for proper enum-based lookups merged[network][staticTokenKey] = mergedToken; - + // Also store under normalized symbol if different from the key, for symbol-based lookups if (normalizedSymbol !== staticTokenKey) { merged[network][normalizedSymbol] = mergedToken; @@ -197,10 +197,10 @@ function mergeWithStaticConfig( ...staticToken, isFromStaticConfig: true }; - + // Store under the static config key (enum value) merged[network][staticTokenKey] = staticTokenWithFlag; - + // Also store under normalized symbol if different from the key if (normalizedSymbol !== staticTokenKey) { merged[network][normalizedSymbol] = staticTokenWithFlag; From 43e48c566e862c58eedbff74edeba249053a5e18 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 6 Feb 2026 18:07:41 +0000 Subject: [PATCH 14/19] Improve documentation: clarify token aliasing behavior Co-authored-by: ebma <6690623+ebma@users.noreply.github.com> --- packages/shared/src/tokens/evm/dynamicEvmTokens.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/shared/src/tokens/evm/dynamicEvmTokens.ts b/packages/shared/src/tokens/evm/dynamicEvmTokens.ts index 749ccdd8c..d2a6640e3 100644 --- a/packages/shared/src/tokens/evm/dynamicEvmTokens.ts +++ b/packages/shared/src/tokens/evm/dynamicEvmTokens.ts @@ -148,6 +148,10 @@ function groupTokensByNetwork(tokens: EvmTokenDetails[]): Record>> From 1b0959f6c7779248f6bd6cdef79b50c697842f09 Mon Sep 17 00:00:00 2001 From: Marcel Ebert Date: Fri, 6 Feb 2026 18:12:52 +0000 Subject: [PATCH 15/19] Revert change in helpers.ts --- packages/shared/src/tokens/utils/helpers.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/shared/src/tokens/utils/helpers.ts b/packages/shared/src/tokens/utils/helpers.ts index abd74f320..6fd5d4a0c 100644 --- a/packages/shared/src/tokens/utils/helpers.ts +++ b/packages/shared/src/tokens/utils/helpers.ts @@ -54,6 +54,14 @@ export function getOnChainTokenDetailsOrDefault( onChainToken: OnChainToken, dynamicEvmTokenConfig?: Record>> ): OnChainTokenDetails { + // AXLUSDC doesn't exist Ethereum + if (onChainToken === EvmToken.AXLUSDC && network === Networks.Ethereum) { + const usdcDetails = getOnChainTokenDetails(network, EvmToken.USDC, dynamicEvmTokenConfig); + if (usdcDetails) { + return usdcDetails; + } + } + const maybeOnChainTokenDetails = getOnChainTokenDetails(network, onChainToken, dynamicEvmTokenConfig); if (maybeOnChainTokenDetails) { return maybeOnChainTokenDetails; From b7ca318bbb05d31a2cdbce2ed074f5ba87e64b6e Mon Sep 17 00:00:00 2001 From: Marcel Ebert Date: Fri, 6 Feb 2026 18:08:54 +0000 Subject: [PATCH 16/19] Add new migration --- .../022-update-subsidy-token-enum.ts | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 apps/api/src/database/migrations/022-update-subsidy-token-enum.ts diff --git a/apps/api/src/database/migrations/022-update-subsidy-token-enum.ts b/apps/api/src/database/migrations/022-update-subsidy-token-enum.ts new file mode 100644 index 000000000..58eb255c1 --- /dev/null +++ b/apps/api/src/database/migrations/022-update-subsidy-token-enum.ts @@ -0,0 +1,58 @@ +import { QueryInterface } from "sequelize"; + +const OLD_ENUM_VALUES = ["GLMR", "PEN", "XLM", "axlUSDC", "BRLA", "EURC"]; +const NEW_ENUM_VALUES = ["GLMR", "PEN", "XLM", "USDC.axl", "BRLA", "EURC", "USDC", "MATIC", "BRL"]; + +export async function up(queryInterface: QueryInterface): Promise { + // Phase 1: Convert enum to VARCHAR to allow value updates + await queryInterface.sequelize.query(` + ALTER TABLE subsidies ALTER COLUMN token TYPE VARCHAR(32); + `); + + // Phase 2: Rename axlUSDC to USDC.axl + await queryInterface.sequelize.query(` + UPDATE subsidies SET token = 'USDC.axl' WHERE token = 'axlUSDC'; + `); + + // Phase 3: Replace enum type with updated values + await queryInterface.sequelize.query(` + DROP TYPE IF EXISTS enum_subsidies_token; + `); + + await queryInterface.sequelize.query(` + CREATE TYPE enum_subsidies_token AS ENUM (${NEW_ENUM_VALUES.map(value => `'${value}'`).join(", ")}); + `); + + await queryInterface.sequelize.query(` + ALTER TABLE subsidies ALTER COLUMN token TYPE enum_subsidies_token USING token::enum_subsidies_token; + `); +} + +export async function down(queryInterface: QueryInterface): Promise { + // Phase 1: Convert enum to VARCHAR to allow value updates + await queryInterface.sequelize.query(` + ALTER TABLE subsidies ALTER COLUMN token TYPE VARCHAR(32); + `); + + // Phase 2: Map unsupported values back to axlUSDC for the old enum + await queryInterface.sequelize.query(` + UPDATE subsidies SET token = 'axlUSDC' WHERE token = 'USDC.axl'; + `); + + await queryInterface.sequelize.query(` + UPDATE subsidies SET token = 'axlUSDC' WHERE token IN ('USDC', 'MATIC', 'BRL'); + `); + + // Phase 3: Restore old enum type + await queryInterface.sequelize.query(` + DROP TYPE IF EXISTS enum_subsidies_token; + `); + + await queryInterface.sequelize.query(` + CREATE TYPE enum_subsidies_token AS ENUM (${OLD_ENUM_VALUES.map(value => `'${value}'`).join(", ")}); + `); + + await queryInterface.sequelize.query(` + ALTER TABLE subsidies ALTER COLUMN token TYPE enum_subsidies_token USING token::enum_subsidies_token; + `); +} From f069beea0f79059b0732bd3c88224ea2129fd3dd Mon Sep 17 00:00:00 2001 From: Marcel Ebert Date: Fri, 6 Feb 2026 18:19:32 +0000 Subject: [PATCH 17/19] Normalize axlUSDC currency --- apps/api/src/api/middlewares/validators.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/apps/api/src/api/middlewares/validators.ts b/apps/api/src/api/middlewares/validators.ts index 193af3f08..520530626 100644 --- a/apps/api/src/api/middlewares/validators.ts +++ b/apps/api/src/api/middlewares/validators.ts @@ -377,6 +377,11 @@ export const validateSubaccountCreation: RequestHandler = (req, res, next) => { }; export const validateCreateQuoteInput: RequestHandler = (req, res, next) => { + if (req.body) { + req.body.inputCurrency = normalizeAxlUsdcCurrency(req.body.inputCurrency) as CreateQuoteRequest["inputCurrency"]; + req.body.outputCurrency = normalizeAxlUsdcCurrency(req.body.outputCurrency) as CreateQuoteRequest["outputCurrency"]; + } + const { rampType, from, to, inputAmount, inputCurrency, outputCurrency } = req.body; if (!rampType || !from || !to || !inputAmount || !inputCurrency || !outputCurrency) { @@ -397,6 +402,11 @@ export const validateCreateBestQuoteInput: RequestHandler { + if (req.body) { + req.body.inputCurrency = normalizeAxlUsdcCurrency(req.body.inputCurrency) as CreateQuoteRequest["inputCurrency"]; + req.body.outputCurrency = normalizeAxlUsdcCurrency(req.body.outputCurrency) as CreateQuoteRequest["outputCurrency"]; + } + const { rampType, from, to, inputAmount, inputCurrency, outputCurrency } = req.body; if (!rampType || !inputAmount || !inputCurrency || !outputCurrency) { @@ -421,6 +431,12 @@ export const validateCreateBestQuoteInput: RequestHandler { + if (typeof value !== "string") return value; + + return value.toLowerCase() === "axlusdc" ? "USDC.axl" : value; +}; + export const validateGetWidgetUrlInput: RequestHandler = ( req, res, From 1560fa1097caed0be803b163139a32adbc59d42a Mon Sep 17 00:00:00 2001 From: Marcel Ebert Date: Fri, 6 Feb 2026 18:32:50 +0000 Subject: [PATCH 18/19] Refactor token selection sorting to prioritize enum-like keys --- .../TokenSelectionList/helpers.tsx | 30 +++++++++++++++---- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/apps/frontend/src/components/TokenSelection/TokenSelectionList/helpers.tsx b/apps/frontend/src/components/TokenSelection/TokenSelectionList/helpers.tsx index 27b47867c..468d307f0 100644 --- a/apps/frontend/src/components/TokenSelection/TokenSelectionList/helpers.tsx +++ b/apps/frontend/src/components/TokenSelection/TokenSelectionList/helpers.tsx @@ -74,12 +74,30 @@ function getOnChainTokensDefinitionsForNetwork(selectedNetwork: Networks): Exten } else if (isNetworkEVM(selectedNetwork)) { const evmConfig = getEvmTokenConfig(); const networkConfig = evmConfig[selectedNetwork as EvmNetworks] ?? {}; - return Object.entries(networkConfig).map(([key, value]) => ({ - assetIcon: value?.assetSymbol ?? key, - assetSymbol: value?.assetSymbol ?? key, - details: value as OnChainTokenDetails, - fallbackLogoURI: value?.fallbackLogoURI, - logoURI: value?.logoURI, + const byToken = new Map(); + + for (const [key, value] of Object.entries(networkConfig)) { + if (!value) continue; + const token = value as OnChainTokenDetails; + const existingKey = byToken.get(token); + + if (!existingKey) { + byToken.set(token, key); + continue; + } + + // Prefer enum-like keys without dots (e.g., "AXLUSDC" over "USDC.AXL") + if (existingKey.includes(".") && !key.includes(".")) { + byToken.set(token, key); + } + } + + return Array.from(byToken.entries()).map(([details, key]) => ({ + assetIcon: details.assetSymbol ?? key, + assetSymbol: details.assetSymbol ?? key, + details, + fallbackLogoURI: details.fallbackLogoURI, + logoURI: details.logoURI, network: selectedNetwork, networkDisplayName: getNetworkDisplayName(selectedNetwork), type: key as OnChainToken From 9d01197bd4a6468142a341305e2571277570517b Mon Sep 17 00:00:00 2001 From: Marcel Ebert Date: Fri, 6 Feb 2026 18:47:41 +0000 Subject: [PATCH 19/19] Fix type issue --- .../components/TokenSelection/TokenSelectionList/helpers.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/frontend/src/components/TokenSelection/TokenSelectionList/helpers.tsx b/apps/frontend/src/components/TokenSelection/TokenSelectionList/helpers.tsx index 468d307f0..cc1703dbd 100644 --- a/apps/frontend/src/components/TokenSelection/TokenSelectionList/helpers.tsx +++ b/apps/frontend/src/components/TokenSelection/TokenSelectionList/helpers.tsx @@ -6,6 +6,7 @@ import { FiatTokenDetails, getEnumKeyByStringValue, getNetworkDisplayName, + isEvmTokenDetails, isNetworkEVM, moonbeamTokenConfig, Networks, @@ -96,7 +97,7 @@ function getOnChainTokensDefinitionsForNetwork(selectedNetwork: Networks): Exten assetIcon: details.assetSymbol ?? key, assetSymbol: details.assetSymbol ?? key, details, - fallbackLogoURI: details.fallbackLogoURI, + fallbackLogoURI: isEvmTokenDetails(details) ? details.fallbackLogoURI : undefined, logoURI: details.logoURI, network: selectedNetwork, networkDisplayName: getNetworkDisplayName(selectedNetwork),