Skip to content
Open
Show file tree
Hide file tree
Changes from all 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 { 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 @@
});

// 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 Expand Up @@ -288,7 +285,7 @@
popular: popularChainsData,
all: allChainsData,
} as DisplayedChains;
}, [chainSearch, crossChainRoutes, otherToken, isOriginToken]);

Check warning on line 288 in src/views/SwapAndBridge/components/ChainTokenSelector/ChainTokenSelectorModal.tsx

View workflow job for this annotation

GitHub Actions / format-and-lint

React Hook useMemo has an unnecessary dependency: 'otherToken'. Either exclude it or remove the dependency array

return isMobile ? (
<MobileModal
Expand Down Expand Up @@ -490,7 +487,7 @@

// Mobile Layout Component - 2-step process
const MobileLayout = ({
isOriginToken,

Check warning on line 490 in src/views/SwapAndBridge/components/ChainTokenSelector/ChainTokenSelectorModal.tsx

View workflow job for this annotation

GitHub Actions / format-and-lint

'isOriginToken' is defined but never used. Allowed unused args must match /^_/u
mobileStep,
selectedChain,
chainSearch,
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";

Check warning on line 6 in src/views/SwapAndBridge/components/ChainTokenSelector/isTokenUnreachable.ts

View workflow job for this annotation

GitHub Actions / format-and-lint

'solana' is defined but never used
import { EnrichedToken } from "./ChainTokenSelectorModal";

export type RouteParams = {
Expand Down Expand Up @@ -38,14 +43,6 @@
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 @@
);
}

/**
* 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;
Copy link
Contributor

Choose a reason for hiding this comment

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

knit !(isSolanaOrigin || isSolanaDestination) is equal to (!isSolanaOrigin && !isSolanaDestination) but might be easier to read? 🤔


// 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 @@
})
: 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