Skip to content
Merged
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
2 changes: 1 addition & 1 deletion src/config/mainnet/tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ export const MAINNET_TOKENS: TokenConfig[] = [
tokenId: { chain: 'Solana', address: 'native' },
},
{
symbol: 'WSOL',
symbol: 'SOL',
decimals: 9,
tokenId: {
chain: 'Solana',
Expand Down
1 change: 0 additions & 1 deletion src/hooks/useFetchSupportedRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@ const useFetchSupportedRoutes = ({

try {
supported = await route.isRouteSupported(
name,
sourceToken,
destToken,
fromChain,
Expand Down
29 changes: 19 additions & 10 deletions src/hooks/useTokenListWithSearch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ import type { Token } from 'config/tokens';
import config from 'config';
import { useTokens } from 'contexts/TokensContext';
import { getTokenSymbol } from 'utils';
import type { Balances } from 'utils/wallet/types';
import {
getWrappedNativeToken,
shouldFilterSameChainToken,
} from 'utils/wrappedNativeTokens';
import { unionBy } from 'es-toolkit';

interface UseTokenListWithSearchParams {
Expand All @@ -22,8 +25,6 @@ interface UseTokenListWithSearchParams {
isSource: boolean;
isSameChainSwap: boolean;
sourceToken?: Token;
balances: Balances;
walletAddress: string;
tokenPastingEnabled?: boolean;
}

Expand All @@ -46,8 +47,6 @@ export const useTokenListWithSearch = ({
isSource,
isSameChainSwap,
sourceToken,
balances,
walletAddress,
tokenPastingEnabled = true,
}: UseTokenListWithSearchParams): UseTokenListWithSearchReturn => {
const [searchedTokens, setSearchedTokens] = useState<Token[]>([]);
Expand All @@ -60,6 +59,15 @@ export const useTokenListWithSearch = ({
setSearchedTokens((prev) => [...prev, token]);
}, []);

// Get wrapped native address for same-chain swaps (only for destination selection)
const wrappedNativeAddr = useMemo(() => {
if (!isSameChainSwap || !chain || isSource) {
return undefined;
}
const wrapped = getWrappedNativeToken(config.network, chain);
return wrapped?.toLowerCase();
}, [isSameChainSwap, chain, isSource]);

useEffect(() => {
if (!chain || !tokenPastingEnabled || !deferredSearch) {
setSearchedTokens([]);
Expand Down Expand Up @@ -139,10 +147,10 @@ export const useTokenListWithSearch = ({
});
}

// For destination token list in same-chain swaps, filter out the source token
if (!isSource && isSameChainSwap && sourceToken) {
// For destination token list in same-chain swaps, filter out invalid options
if (sourceToken && isSameChainSwap && !isSource) {
tokens = tokens.filter(
(t) => t.addressString !== sourceToken.addressString,
(t) => !shouldFilterSameChainToken(sourceToken, wrappedNativeAddr, t),
);
}

Expand All @@ -152,9 +160,10 @@ export const useTokenListWithSearch = ({
searchedTokens,
deferredSearch,
searchLower,
isSource,
isSameChainSwap,
sourceToken,
isSameChainSwap,
isSource,
wrappedNativeAddr,
]);

const tokenPrices = useMemo(
Expand Down
3 changes: 1 addition & 2 deletions src/routes/operator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,10 +146,9 @@ export default class RouteOperator {
): Promise<TokenId[]> {
const supported: Set<string> = new Set();

await this.forEach(async (name, route) => {
await this.forEach(async (_, route) => {
try {
const destTokenIds = await route.supportedDestTokens(
name,
sourceToken,
sourceChain,
destChain,
Expand Down
16 changes: 11 additions & 5 deletions src/routes/sdkv2/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ import config, { getWormholeContextV2 } from 'config';
import { sleep } from 'utils';
import { isFrankensteinToken } from 'utils';
import { isNttToken } from 'utils/ntt';
import {
getWrappedNativeToken,
shouldFilterSameChainToken,
} from 'utils/wrappedNativeTokens';

type Amount = sdkAmount.Amount;

Expand Down Expand Up @@ -61,7 +65,6 @@ export class SDKv2Route {
}

async isRouteSupported(
name: string,
sourceToken: Token,
destToken: Token,
fromChain: Chain,
Expand All @@ -88,7 +91,6 @@ export class SDKv2Route {

try {
const supportedDestinationTokens = await this.supportedDestTokens(
name,
sourceToken,
fromChain,
toChain,
Expand All @@ -108,7 +110,6 @@ export class SDKv2Route {
}

async supportedDestTokens(
routeName: string,
sourceToken: Token | undefined,
fromChain?: Chain | undefined,
toChain?: Chain | undefined,
Expand Down Expand Up @@ -148,14 +149,19 @@ export class SDKv2Route {
routeSupportedTokenFetcher,
);

// Get wrapped native address for same-chain swaps
const wrappedNativeAddr = isSameChain
? getWrappedNativeToken(config.network, toContext.chain)?.toLowerCase()
: undefined;

const filteredTokens = destTokens.filter((t) => {
const token = config.tokens.get(t);
if (token && isFrankensteinToken(token, toContext.chain)) {
return false;
}

if (isSameChain && token?.address === sourceToken.address) {
return false;
if (isSameChain) {
return !shouldFilterSameChainToken(sourceToken, wrappedNativeAddr, t);
}

return true;
Expand Down
5 changes: 3 additions & 2 deletions src/utils/sdkv2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
TBTCBridge,
chainToPlatform,
} from '@wormhole-foundation/sdk';
import { getWrappedNativeToken } from './wrappedNativeTokens';
import type { NttRoute } from '@wormhole-foundation/sdk-route-ntt';
import type { CCTPv2ExecutorRoute } from '@wormhole-labs/cctp-executor-route';
import { Connection } from '@solana/web3.js';
Expand Down Expand Up @@ -559,10 +560,10 @@ const getTokenBridgeToken = async (
address: token.address,
});

const wrappedNative = await tb.getWrappedNative();
const wrappedNative = getWrappedNativeToken(config.network, chain);

const tokenId =
wrappedNative.toString() === tokenAddress.toString()
wrappedNative && wrappedNative === tokenAddress.toString()
? nativeTokenId(chain)
: Wormhole.tokenId(chain, tokenAddress.toString());

Expand Down
58 changes: 58 additions & 0 deletions src/utils/wrappedNativeTokens.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import type { Chain, Network, TokenAddress } from '@wormhole-foundation/sdk';
import { chainToPlatform, isNative } from '@wormhole-foundation/sdk';
import { WETH_CONTRACTS } from '@wormhole-foundation/sdk-evm';

const WSOL_ADDRESS = 'So11111111111111111111111111111111111111112';

export function getWrappedNativeToken(
network: Network,
chain: Chain,
): string | undefined {
const platform = chainToPlatform(chain);

if (platform === 'Evm') {
return WETH_CONTRACTS[network]?.[chain];
}

if (platform === 'Solana') {
return WSOL_ADDRESS;
}

return undefined;
}

/**
* Determines if a token should be filtered out in same-chain swaps
* based on native/wrapped token pair restrictions
*/
export function shouldFilterSameChainToken(
sourceToken: { address: TokenAddress<Chain> } | null,
wrappedNativeAddr: string | undefined,
currentToken: { address: TokenAddress<Chain> },
): boolean {
if (!sourceToken) {
return false;
}

const sourceAddr = sourceToken.address.toString();
const currentAddr = currentToken.address.toString();

if (currentAddr === sourceAddr) {
return true;
}

// Block native-wrapped token pairs if we have wrapped native address
if (wrappedNativeAddr) {
const srcIsNative = isNative(sourceToken.address);
const srcIsWrapped = sourceAddr.toLowerCase() === wrappedNativeAddr;

const destIsWrapped = currentAddr.toLowerCase() === wrappedNativeAddr;
const destIsNative = isNative(currentToken.address);

if ((srcIsNative && destIsWrapped) || (srcIsWrapped && destIsNative)) {
return true;
}
}

return false;
}
2 changes: 0 additions & 2 deletions src/views/v2/Bridge/AssetPicker/TokenList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,6 @@ const TokenList = (props: Props) => {
isSource: props.isSource,
isSameChainSwap: props.isSameChainSwap,
sourceToken: props.sourceToken,
balances: props.balances,
walletAddress: props.wallet.address,
tokenPastingEnabled: tokenPastingIsEnabled,
});

Expand Down
25 changes: 8 additions & 17 deletions src/views/v3/Bridge/AssetPicker/TokenList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,6 @@ const TokenList = (props: Props) => {
isSource: props.isSource,
isSameChainSwap: props.isSameChainSwap,
sourceToken: props.sourceToken,
balances: props.balances,
walletAddress: props.wallet.address,
tokenPastingEnabled: tokenPastingIsEnabled,
});

Expand Down Expand Up @@ -78,15 +76,6 @@ const TokenList = (props: Props) => {
tokenListContainer: {
padding: '16px 0 0 0 !important',
},
title: {
fontSize: 14,
marginBottom: '8px',
},
tokenLoaderRow: {
display: 'flex',
justifyContent: 'space-between',
padding: '8px 16px',
},
tokenList: {
maxHeight: '360px',
[theme.breakpoints.down('sm')]: {
Expand Down Expand Up @@ -147,15 +136,17 @@ const TokenList = (props: Props) => {
const isRestSection =
isGroupingEnabled && isWalletConnected && index >= ownedCount;

const headerLabel = (() => {
if (!isGroupingEnabled) return null;
if (index === 0 && ownedCount > 0) return 'Your tokens';
if (index === ownedCount) return 'All tokens';
return null;
})();

return (
<Fragment key={token.key}>
<TokenSectionHeader
index={index}
ownedCount={ownedCount}
isGroupingEnabled={isGroupingEnabled}
/>
{headerLabel && <TokenSectionHeader label={headerLabel} />}
<TokenItem
key={token.key}
token={token}
chain={props.selectedChainConfig.sdkName}
onClick={() => props.onSelectToken(token)}
Expand Down
28 changes: 1 addition & 27 deletions src/views/v3/Bridge/AssetPicker/TokenSectionHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,9 @@ import { Box, useTheme } from '@mui/material';
import { Typography } from '@mui/material';
import React from 'react';

const TokenSectionHeader = ({
index,
ownedCount,
isGroupingEnabled,
}: {
index: number;
ownedCount: number;
isGroupingEnabled: boolean;
}) => {
const TokenSectionHeader = ({ label }: { label: string }) => {
const theme = useTheme();

if (!isGroupingEnabled) {
return null;
}

let label: string | null = null;

if (index === 0 && ownedCount > 0) {
label = 'Your tokens';
}

if (index === ownedCount) {
label = 'All tokens';
}

if (!label) {
return null;
}

return (
<Box
sx={{
Expand Down
Loading