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/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, diff --git a/apps/api/src/api/services/hydration/swap.ts b/apps/api/src/api/services/hydration/swap.ts index b39e239d8..fd6cc6854 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.Omni, PoolType.Stable, PoolType.Aave] } + }); }); // Refresh transaction fees every hour 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; + `); +} 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/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/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/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] ); diff --git a/apps/frontend/src/components/TokenSelection/TokenSelectionList/helpers.tsx b/apps/frontend/src/components/TokenSelection/TokenSelectionList/helpers.tsx index 27ae49229..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, @@ -63,7 +64,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, @@ -74,12 +75,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?.logoURI ?? value?.networkAssetIcon ?? "", - 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: isEvmTokenDetails(details) ? details.fallbackLogoURI : undefined, + logoURI: details.logoURI, network: selectedNetwork, networkDisplayName: getNetworkDisplayName(selectedNetwork), type: key as OnChainToken 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({ > =2.0.0" } }, "sha512-Z4aHi3ECFf5oWYWM3F1rW83GJfB9OvhBYPTmb5q+VyK3uvzvS48lwo+jwh2eOoCRWEuT/crpb9Vwp2QaS5JqgQ=="], @@ -3439,8 +3437,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=="], 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..d45aec70b 100644 --- a/packages/shared/src/tokens/evm/config.ts +++ b/packages/shared/src/tokens/evm/config.ts @@ -11,41 +11,28 @@ 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 @@ -154,6 +147,11 @@ function groupTokensByNetwork(tokens: EvmTokenDetails[]): Record>> @@ -166,30 +164,51 @@ function mergeWithStaticConfig( const networkTokenConfig = evmTokenConfig[network]; if (!networkTokenConfig) return; - for (const [symbol, staticToken] of Object.entries(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 = 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.` ); } - // Static token exists and dynamic token exists - merge, static takes priority - merged[network][normalizedSymbol] = { + // Static token exists and dynamic token exists - merge, static takes priority, mark as static + const mergedToken = { ...staticToken, - fallbackLogoURI: staticToken.fallbackLogoURI ?? dynamicToken.fallbackLogoURI, + 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 - merged[network][normalizedSymbol] = staticToken; + // Static token exists but no dynamic token - use static as-is, mark as static + 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/moonbeam/config.ts b/packages/shared/src/tokens/moonbeam/config.ts index a47462ddc..6ad9a36ed 100644 --- a/packages/shared/src/tokens/moonbeam/config.ts +++ b/packages/shared/src/tokens/moonbeam/config.ts @@ -12,12 +12,11 @@ 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, network: Networks.Moonbeam, - networkAssetIcon: "moonbeamUSDC", pendulumRepresentative: PENDULUM_USDC_AXL, type: TokenType.Evm }; 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 { diff --git a/packages/shared/src/tokens/utils/helpers.ts b/packages/shared/src/tokens/utils/helpers.ts index 14ca9fa9e..6fd5d4a0c 100644 --- a/packages/shared/src/tokens/utils/helpers.ts +++ b/packages/shared/src/tokens/utils/helpers.ts @@ -10,7 +10,7 @@ import { getEvmTokenConfig } from "../evm/dynamicEvmTokens"; import { moonbeamTokenConfig } from "../moonbeam/config"; import { stellarTokenConfig } from "../stellar/config"; import { AssetHubToken, FiatToken, OnChainToken, RampCurrency } from "../types/base"; -import { EvmTokenDetails } from "../types/evm"; +import { EvmToken, EvmTokenDetails } from "../types/evm"; import { MoonbeamTokenDetails } from "../types/moonbeam"; import { PendulumTokenDetails } from "../types/pendulum"; import { StellarTokenDetails } from "../types/stellar"; @@ -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;