Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ import useCurrentBreakpoint from "hooks/useCurrentBreakpoint";
import { BigNumber } from "ethers";
import { Text, TokenImage } from "components";
import { useHotkeys } from "react-hotkeys-hook";
import { getBridgeableSvmTokenFilterPredicate } from "./getBridgeableSvmTokenFilterPredicate";
import { isTokenUnreachable } from "./isTokenUnreachable";
import { useTrackChainSelected } from "./useTrackChainSelected";
import { useTrackTokenSelected } from "./useTrackTokenSelected";
Expand Down Expand Up @@ -148,27 +147,25 @@ export function ChainTokenSelectorModal({
});

// Filter by search first
const filteredTokens = enrichedTokens
.filter((t) => {
// First filter by selected chain
if (selectedChain !== null && t.chainId !== selectedChain) {
return false;
}
const filteredTokens = enrichedTokens.filter((t) => {
// First filter by selected chain
if (selectedChain !== null && t.chainId !== selectedChain) {
return false;
}

if (tokenSearch === "") {
return true;
}
if (tokenSearch === "") {
return true;
}

const keywords = [
t.symbol.toLowerCase().replaceAll(" ", ""),
t.name.toLowerCase().replaceAll(" ", ""),
t.address.toLowerCase().replaceAll(" ", ""),
];
return keywords.some((keyword) =>
keyword.includes(tokenSearch.toLowerCase().replaceAll(" ", ""))
);
})
.filter(getBridgeableSvmTokenFilterPredicate(isOriginToken, otherToken));
const keywords = [
t.symbol.toLowerCase().replaceAll(" ", ""),
t.name.toLowerCase().replaceAll(" ", ""),
t.address.toLowerCase().replaceAll(" ", ""),
];
return keywords.some((keyword) =>
keyword.includes(tokenSearch.toLowerCase().replaceAll(" ", ""))
);
});

// Sort function that prioritizes tokens with balance, then by balance amount, then alphabetically
const sortTokens = (tokens: EnrichedTokenWithReachability[]) => {
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -195,36 +195,92 @@ describe("isTokenUnreachable", () => {
});
});

it("should restrict ANY output tokens to Solana", () => {
const inputToken = {
chainId: CHAIN_IDs.MAINNET,
symbol: "USDC",
} as EnrichedToken;
describe("bridgeable SVM token restrictions", () => {
describe("when Solana is origin chain", () => {
it("should mark non-bridgeable destination tokens as unreachable", () => {
const originToken = {
chainId: CHAIN_IDs.SOLANA,
symbol: "USDC",
} as EnrichedToken;

const destinationToken = {
chainId: CHAIN_IDs.MAINNET,
symbol: "ETH", // not bridgeable
} as EnrichedToken;

// Selecting destination token (isOriginToken = false)
const isUnreachable = isTokenUnreachable(
destinationToken,
false,
originToken
);

expect(isUnreachable).toBe(true);
});

const outputToken = {
chainId: CHAIN_IDs.SOLANA,
symbol: "USDT", // not bridgeable
} as EnrichedToken;
it("should NOT mark bridgeable destination tokens as unreachable", () => {
const originToken = {
chainId: CHAIN_IDs.SOLANA,
symbol: "USDC",
} as EnrichedToken;

const isUnreachable = isTokenUnreachable(inputToken, true, outputToken);
const destinationToken = {
chainId: CHAIN_IDs.MAINNET,
symbol: "USDC", // bridgeable
} as EnrichedToken;

expect(isUnreachable).toBe(true);
});
const isUnreachable = isTokenUnreachable(
destinationToken,
false,
originToken
);

it("should NOT restrict BRIDGEABLE output tokens to Solana", () => {
const inputToken = {
chainId: CHAIN_IDs.MAINNET,
symbol: "USDC",
} as EnrichedToken;
expect(isUnreachable).toBe(false);
});
});

const outputToken = {
chainId: CHAIN_IDs.SOLANA,
symbol: "USDC", // not bridgeable
} as EnrichedToken;
describe("when Solana is destination chain", () => {
it("should mark non-bridgeable destination tokens as unreachable", () => {
const originToken = {
chainId: CHAIN_IDs.MAINNET,
symbol: "USDC",
} as EnrichedToken;

const destinationToken = {
chainId: CHAIN_IDs.SOLANA,
symbol: "ETH", // not bridgeable
} as EnrichedToken;

// Selecting destination token (isOriginToken = false)
const isUnreachable = isTokenUnreachable(
destinationToken,
false,
originToken
);

expect(isUnreachable).toBe(true);
});

const isUnreachable = isTokenUnreachable(inputToken, true, outputToken);
it("should NOT mark bridgeable destination tokens as unreachable", () => {
const originToken = {
chainId: CHAIN_IDs.MAINNET,
symbol: "USDC",
} as EnrichedToken;

expect(isUnreachable).toBe(false);
const destinationToken = {
chainId: CHAIN_IDs.SOLANA,
symbol: "USDC", // bridgeable
} as EnrichedToken;

const isUnreachable = isTokenUnreachable(
destinationToken,
false,
originToken
);

expect(isUnreachable).toBe(false);
});
});
});

describe("matchesGlob", () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { CHAIN_IDs, INDIRECT_CHAINS } from "../../../../utils/constants";
import {
CHAIN_IDs,
INDIRECT_CHAINS,
interchangeableTokensMap,
} from "../../../../utils/constants";
import { solana } from "../../../../constants/chains/configs";
import { EnrichedToken } from "./ChainTokenSelectorModal";

export type RouteParams = {
Expand Down Expand Up @@ -38,14 +43,6 @@ const RESTRICTED_ROUTES: RestrictedRoute[] = [
toChainId: [CHAIN_IDs.HYPERCORE],
toSymbol: ["USDC-SPOT"],
},

// only allow bridegable output to SOlana
{
fromChainId: "*",
fromSymbol: ["*"],
toChainId: [CHAIN_IDs.SOLANA],
toSymbol: ["!USDC"],
},
];

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would it be better if we extended the RESTRICTED_ROUTES to support multiple to symbols?

{
    fromChainId: "*",
    fromSymbol: ["*"],
    toChainId: [CHAIN_IDs.SOLANA],
    toSymbol: ["!USDC", "!USDH", "!USDH-SPOT", "!USDC-SPOT", "!USDT-SPOT"],
  }

// simple glob tester. supports only: ["*" , "!"]
Expand Down Expand Up @@ -112,6 +109,45 @@ function getRestrictedOriginChainsUnreachable(
);
}

/**
* Checks if a token should be marked unreachable when Solana is involved.
* When Solana is either origin or destination chain, only bridgeable tokens
* should be allowed as output tokens.
*/
function isNonBridgeableSvmTokenUnreachable(
token: EnrichedToken,
isOriginToken: boolean,
otherToken: EnrichedToken | null | undefined
): boolean {
// Only apply this check when selecting destination tokens (output tokens)
if (isOriginToken) return false;

// Check if Solana is either origin or destination chain
const isSolanaOrigin = otherToken?.chainId === CHAIN_IDs.SOLANA;
const isSolanaDestination = token.chainId === CHAIN_IDs.SOLANA;

// If Solana is not involved, don't mark as unreachable
if (!(isSolanaOrigin || isSolanaDestination)) return false;

// If Solana is involved, check if token is bridgeable
const bridgeableSvmTokenSymbols = [
"USDC",
"USDH",
"USDH-SPOT",
"USDC-SPOT",
"USDT-SPOT",
];

const isBridgeable =
bridgeableSvmTokenSymbols.includes(token.symbol) ||
bridgeableSvmTokenSymbols.some((symbol) =>
interchangeableTokensMap[token.symbol]?.includes(symbol)
);

// Mark as unreachable if not bridgeable
return !isBridgeable;
}

/**
* Determines if a token is unreachable based on various criteria.
*
Expand Down Expand Up @@ -144,6 +180,15 @@ export function isTokenUnreachable(
})
: false;

// Check if token should be unreachable due to Solana bridgeable token restrictions
const isNonBridgeableSvm = isNonBridgeableSvmTokenUnreachable(
token,
isOriginToken,
otherToken
);

// Combine all unreachability checks
return isSameChain || isRestrictedOrigin || isRestrictedRoute;
return (
isSameChain || isRestrictedOrigin || isRestrictedRoute || isNonBridgeableSvm
);
}
Loading