Skip to content

Commit 1d4ff54

Browse files
committed
chore: prevent native and wrapped token swaps on same chain
1 parent 6c07e74 commit 1d4ff54

6 files changed

Lines changed: 81 additions & 32 deletions

File tree

src/hooks/useTokenListWithSearch.ts

Lines changed: 51 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@ import {
66
useDeferredValue,
77
startTransition,
88
} from 'react';
9-
import { toNative } from '@wormhole-foundation/sdk';
9+
import { toNative, isNative } from '@wormhole-foundation/sdk';
1010
import type { Chain } from '@wormhole-foundation/sdk';
1111
import type { Token } from 'config/tokens';
1212
import config from 'config';
1313
import { useTokens } from 'contexts/TokensContext';
1414
import { getTokenSymbol } from 'utils';
15-
import type { Balances } from 'utils/wallet/types';
15+
import { getWrappedNativeToken } from 'utils/wrappedNativeTokens';
1616
import { unionBy } from 'es-toolkit';
1717

1818
interface UseTokenListWithSearchParams {
@@ -22,8 +22,6 @@ interface UseTokenListWithSearchParams {
2222
isSource: boolean;
2323
isSameChainSwap: boolean;
2424
sourceToken?: Token;
25-
balances: Balances;
26-
walletAddress: string;
2725
tokenPastingEnabled?: boolean;
2826
}
2927

@@ -46,8 +44,6 @@ export const useTokenListWithSearch = ({
4644
isSource,
4745
isSameChainSwap,
4846
sourceToken,
49-
balances,
50-
walletAddress,
5147
tokenPastingEnabled = true,
5248
}: UseTokenListWithSearchParams): UseTokenListWithSearchReturn => {
5349
const [searchedTokens, setSearchedTokens] = useState<Token[]>([]);
@@ -60,6 +56,15 @@ export const useTokenListWithSearch = ({
6056
setSearchedTokens((prev) => [...prev, token]);
6157
}, []);
6258

59+
// Get wrapped native address for same-chain swaps (only for destination selection)
60+
const wrappedNativeAddr = useMemo(() => {
61+
if (!isSameChainSwap || !chain || isSource) {
62+
return undefined;
63+
}
64+
const wrapped = getWrappedNativeToken(config.network, chain);
65+
return wrapped?.toLowerCase();
66+
}, [isSameChainSwap, chain, isSource]);
67+
6368
useEffect(() => {
6469
if (!chain || !tokenPastingEnabled || !deferredSearch) {
6570
setSearchedTokens([]);
@@ -103,6 +108,20 @@ export const useTokenListWithSearch = ({
103108
addTokenIfNotExists,
104109
]);
105110

111+
const sourceTokenInfo = useMemo(() => {
112+
if (!sourceToken || !isSameChainSwap || isSource) {
113+
return null;
114+
}
115+
116+
return {
117+
addressString: sourceToken.addressString,
118+
isNative: isNative(sourceToken.address),
119+
isWrapped: wrappedNativeAddr
120+
? sourceToken.addressString.toLowerCase() === wrappedNativeAddr
121+
: false,
122+
};
123+
}, [sourceToken, isSameChainSwap, isSource, wrappedNativeAddr]);
124+
106125
const sortedTokens = useMemo(() => {
107126
// Merge base tokens with any fetched tokens only when searching
108127
let tokens = deferredSearch
@@ -139,11 +158,30 @@ export const useTokenListWithSearch = ({
139158
});
140159
}
141160

142-
// For destination token list in same-chain swaps, filter out the source token
143-
if (!isSource && isSameChainSwap && sourceToken) {
144-
tokens = tokens.filter(
145-
(t) => t.addressString !== sourceToken.addressString,
146-
);
161+
// For destination token list in same-chain swaps, filter out invalid options
162+
if (sourceTokenInfo) {
163+
tokens = tokens.filter((t) => {
164+
// Filter out exact same token
165+
if (t.addressString === sourceTokenInfo.addressString) {
166+
return false;
167+
}
168+
169+
// Block native-wrapped token pairs if we have wrapped native address
170+
if (wrappedNativeAddr) {
171+
const destIsWrapped =
172+
t.addressString.toLowerCase() === wrappedNativeAddr;
173+
const destIsNative = isNative(t.address);
174+
175+
if (
176+
(sourceTokenInfo.isNative && destIsWrapped) ||
177+
(sourceTokenInfo.isWrapped && destIsNative)
178+
) {
179+
return false;
180+
}
181+
}
182+
183+
return true;
184+
});
147185
}
148186

149187
return tokens;
@@ -152,9 +190,8 @@ export const useTokenListWithSearch = ({
152190
searchedTokens,
153191
deferredSearch,
154192
searchLower,
155-
isSource,
156-
isSameChainSwap,
157-
sourceToken,
193+
sourceTokenInfo,
194+
wrappedNativeAddr,
158195
]);
159196

160197
const tokenPrices = useMemo(

src/routes/sdkv2/route.ts

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import config, { getWormholeContextV2 } from 'config';
2222
import { sleep } from 'utils';
2323
import { isFrankensteinToken } from 'utils';
2424
import { isNttToken } from 'utils/ntt';
25+
import { getWrappedNativeToken } from 'utils/wrappedNativeTokens';
2526

2627
type Amount = sdkAmount.Amount;
2728

@@ -146,18 +147,10 @@ export class SDKv2Route {
146147
routeSupportedTokenFetcher,
147148
);
148149

149-
// Pre-compute wrapped native address when filtering same-chain swaps
150-
let wrappedNativeAddr: string | undefined = undefined;
151-
if (isSameChain) {
152-
try {
153-
const tb = await toContext.context.getTokenBridge();
154-
wrappedNativeAddr = (await tb.getWrappedNative())
155-
.toString()
156-
.toLowerCase();
157-
} catch {
158-
// No-op
159-
}
160-
}
150+
// Get wrapped native address for same-chain swaps
151+
const wrappedNativeAddr = isSameChain
152+
? getWrappedNativeToken(config.network, toContext.chain)?.toLowerCase()
153+
: undefined;
161154

162155
const filteredTokens = destTokens.filter((t) => {
163156
const token = config.tokens.get(t);

src/utils/sdkv2.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
TBTCBridge,
2222
chainToPlatform,
2323
} from '@wormhole-foundation/sdk';
24+
import { getWrappedNativeToken } from './wrappedNativeTokens';
2425
import type { NttRoute } from '@wormhole-foundation/sdk-route-ntt';
2526
import type { CCTPv2ExecutorRoute } from '@wormhole-labs/cctp-executor-route';
2627
import { Connection } from '@solana/web3.js';
@@ -559,10 +560,10 @@ const getTokenBridgeToken = async (
559560
address: token.address,
560561
});
561562

562-
const wrappedNative = await tb.getWrappedNative();
563+
const wrappedNative = getWrappedNativeToken(config.network, chain);
563564

564565
const tokenId =
565-
wrappedNative.toString() === tokenAddress.toString()
566+
wrappedNative && wrappedNative === tokenAddress.toString()
566567
? nativeTokenId(chain)
567568
: Wormhole.tokenId(chain, tokenAddress.toString());
568569

src/utils/wrappedNativeTokens.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import type { Chain, Network } from '@wormhole-foundation/sdk';
2+
import { chainToPlatform } from '@wormhole-foundation/sdk';
3+
import { WETH_CONTRACTS } from '@wormhole-foundation/sdk-evm';
4+
5+
const WSOL_ADDRESS = 'So11111111111111111111111111111111111111112';
6+
7+
export function getWrappedNativeToken(
8+
network: Network,
9+
chain: Chain,
10+
): string | undefined {
11+
const platform = chainToPlatform(chain);
12+
13+
if (platform === 'Evm') {
14+
return WETH_CONTRACTS[network]?.[chain];
15+
}
16+
17+
if (platform === 'Solana') {
18+
return WSOL_ADDRESS;
19+
}
20+
21+
return undefined;
22+
}

src/views/v2/Bridge/AssetPicker/TokenList.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,6 @@ const TokenList = (props: Props) => {
4242
isSource: props.isSource,
4343
isSameChainSwap: props.isSameChainSwap,
4444
sourceToken: props.sourceToken,
45-
balances: props.balances,
46-
walletAddress: props.wallet.address,
4745
tokenPastingEnabled: tokenPastingIsEnabled,
4846
});
4947

src/views/v3/Bridge/AssetPicker/TokenList.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,6 @@ const TokenList = (props: Props) => {
4242
isSource: props.isSource,
4343
isSameChainSwap: props.isSameChainSwap,
4444
sourceToken: props.sourceToken,
45-
balances: props.balances,
46-
walletAddress: props.wallet.address,
4745
tokenPastingEnabled: tokenPastingIsEnabled,
4846
});
4947

0 commit comments

Comments
 (0)