Skip to content

Commit dbf2111

Browse files
committed
refactor: set OFT token as the l2 token and canonical as the exception
1 parent 3ec940f commit dbf2111

14 files changed

Lines changed: 454 additions & 220 deletions

File tree

packages/arb-token-bridge-ui/src/app/api/crosschain-transfers/utils.test.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ describe('isValidLifiTransfer', () => {
183183

184184
expect(
185185
isValidLifiTransfer({
186-
fromToken: CommonAddress.Ethereum.PYUSD,
186+
fromToken: CommonAddress.ArbitrumOne.PYUSDCanonical,
187187
sourceChainId: ChainId.ArbitrumOne,
188188
destinationChainId: ChainId.Ethereum,
189189
tokensFromLists,
@@ -194,7 +194,7 @@ describe('isValidLifiTransfer', () => {
194194
it('blocks canonical PYUSD withdrawals without tokensFromLists', () => {
195195
expect(
196196
isValidLifiTransfer({
197-
fromToken: CommonAddress.Ethereum.PYUSD,
197+
fromToken: CommonAddress.ArbitrumOne.PYUSDCanonical,
198198
sourceChainId: ChainId.ArbitrumOne,
199199
destinationChainId: ChainId.Ethereum,
200200
}),
@@ -210,6 +210,16 @@ describe('isValidLifiTransfer', () => {
210210
}),
211211
).toBe(true);
212212
});
213+
214+
it('allows PYUSD OFT withdrawal through LiFi', () => {
215+
expect(
216+
isValidLifiTransfer({
217+
fromToken: CommonAddress.Ethereum.PYUSD,
218+
sourceChainId: ChainId.ArbitrumOne,
219+
destinationChainId: ChainId.Ethereum,
220+
}),
221+
).toBe(true);
222+
});
213223
});
214224

215225
describe('getTokenOverride', () => {

packages/arb-token-bridge-ui/src/app/api/crosschain-transfers/utils.ts

Lines changed: 14 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,9 @@ import { ChainId } from '../../../types/ChainId';
88
import { addressesEqual } from '../../../util/AddressUtils';
99
import { CommonAddress, bridgedUsdcToken, commonUsdcToken } from '../../../util/CommonAddressUtils';
1010
import {
11-
getArbitrumOnePyusdOftToken,
12-
getEthereumPyusdToken,
13-
isTokenArbitrumOnePyusdOft,
14-
isTokenEthereumPyusd,
11+
getPyusdTokenOverride,
12+
isPyusdOverrideFlow,
13+
isTokenArbitrumOnePyusdCanonical,
1514
} from '../../../util/PyusdUtils';
1615
import { allowedLifiSourceChainIds, lifiDestinationChainIds } from './constants';
1716

@@ -66,26 +65,15 @@ export function isValidLifiTransfer({
6665
}
6766

6867
// Canonical PYUSD is only supported through canonical withdraw
69-
if (
70-
isTokenEthereumPyusd(fromToken) &&
71-
sourceChainId === ChainId.ArbitrumOne &&
72-
destinationChainId === ChainId.Ethereum
73-
) {
68+
if (isTokenArbitrumOnePyusdCanonical(fromToken)) {
7469
return false;
7570
}
7671

7772
if (
78-
isTokenEthereumPyusd(fromToken) &&
79-
sourceChainId === ChainId.Ethereum &&
80-
destinationChainId === ChainId.ArbitrumOne
81-
) {
82-
return true;
83-
}
84-
85-
if (
86-
isTokenArbitrumOnePyusdOft(fromToken) &&
87-
sourceChainId === ChainId.ArbitrumOne &&
88-
destinationChainId === ChainId.Ethereum
73+
isPyusdOverrideFlow({
74+
tokenAddress: fromToken,
75+
isDepositMode: sourceChainId === ChainId.Ethereum,
76+
})
8977
) {
9078
return true;
9179
}
@@ -312,26 +300,13 @@ export function getTokenOverride({
312300
}
313301
}
314302

315-
if (
316-
isTokenEthereumPyusd(fromToken) &&
317-
sourceChainId === ChainId.Ethereum &&
318-
destinationChainId === ChainId.ArbitrumOne
319-
) {
320-
return {
321-
source: getEthereumPyusdToken(),
322-
destination: getArbitrumOnePyusdOftToken(),
323-
};
324-
}
303+
const pyusdOverride = getPyusdTokenOverride({
304+
tokenAddress: fromToken,
305+
isDepositMode: sourceChainId === ChainId.Ethereum,
306+
});
325307

326-
if (
327-
isTokenArbitrumOnePyusdOft(fromToken) &&
328-
sourceChainId === ChainId.ArbitrumOne &&
329-
destinationChainId === ChainId.Ethereum
330-
) {
331-
return {
332-
source: getArbitrumOnePyusdOftToken(),
333-
destination: getEthereumPyusdToken(),
334-
};
308+
if (pyusdOverride) {
309+
return pyusdOverride;
335310
}
336311

337312
return {

packages/arb-token-bridge-ui/src/components/TransferPanel/DestinationTokenButton.tsx

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { ChevronDownIcon } from '@heroicons/react/24/outline';
22

3-
import { getTokenOverride, isValidLifiTransfer } from '@/bridge/app/api/crosschain-transfers/utils';
3+
import { isValidLifiTransfer } from '@/bridge/app/api/crosschain-transfers/utils';
44
import { useSelectedToken } from '@/bridge/hooks/useSelectedToken';
55

66
import { useDestinationToken } from '../../hooks/useDestinationToken';
@@ -32,30 +32,17 @@ export function DestinationTokenButton({
3232

3333
const { childChainProvider } = useNetworksRelationship(networks);
3434
const nativeCurrency = useNativeCurrency({ provider: childChainProvider });
35-
const tokenOverride = getTokenOverride({
36-
destinationChainId: networks.destinationChain.id,
37-
fromToken: destinationToken?.address,
38-
sourceChainId: networks.sourceChain.id,
39-
});
4035

4136
const tokenSymbol =
4237
tokenInfo?.symbol ||
43-
(tokenOverride.destination
44-
? sanitizeTokenSymbol(tokenOverride.destination.symbol, {
45-
erc20L1Address: tokenOverride.destination.address,
38+
(destinationToken
39+
? sanitizeTokenSymbol(destinationToken.symbol, {
40+
erc20L1Address: destinationToken.address,
4641
chainId: networks.destinationChain.id,
4742
})
48-
: destinationToken
49-
? destinationToken?.symbol
50-
: nativeCurrency.symbol);
43+
: nativeCurrency.symbol);
5144

52-
const tokenLogoSrc =
53-
tokenInfo?.logoUrl ||
54-
(tokenOverride.destination
55-
? tokenOverride.destination?.logoURI
56-
: destinationToken
57-
? destinationToken?.logoURI
58-
: nativeCurrency.logoUrl);
45+
const tokenLogoSrc = tokenInfo?.logoUrl || destinationToken?.logoURI || nativeCurrency.logoUrl;
5946

6047
return (
6148
<>

packages/arb-token-bridge-ui/src/components/TransferPanel/TokenSearch.tsx

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,6 @@ function TokenListsPanel() {
154154
}
155155

156156
const NATIVE_CURRENCY_IDENTIFIER = 'native_currency';
157-
const ETHEREUM_PYUSD_ADDRESS = CommonAddress.Ethereum.PYUSD.toLowerCase();
158157

159158
function TokensPanel({
160159
onTokenSelected,
@@ -198,28 +197,20 @@ function TokensPanel({
198197

199198
const getWithdrawalPyusdToken = useCallback(
200199
(address: string) => {
201-
if (isDepositMode || !isArbitrumOneEthereumPair) {
200+
if (isDepositMode) {
202201
return null;
203202
}
204203

205-
const pyusdListEntry = tokensFromLists[ETHEREUM_PYUSD_ADDRESS];
204+
const pyusdListEntry = tokensFromLists[CommonAddress.Ethereum.PYUSD];
206205

207206
return getPyusdTokenForTransfer({
208207
tokenAddress: address,
209208
isDepositMode,
210-
sourceChainId: networks.sourceChain.id,
211-
destinationChainId: networks.destinationChain.id,
212209
priceUSD: pyusdListEntry?.priceUSD,
213210
listIds: pyusdListEntry?.listIds,
214211
});
215212
},
216-
[
217-
isDepositMode,
218-
isArbitrumOneEthereumPair,
219-
networks.sourceChain.id,
220-
networks.destinationChain.id,
221-
tokensFromLists,
222-
],
213+
[isDepositMode, tokensFromLists],
223214
);
224215

225216
const getBalance = useCallback(
@@ -314,6 +305,7 @@ function TokensPanel({
314305
tokenAddresses.push(CommonAddress.ArbitrumOne.USDC);
315306
if (isArbitrumOneEthereumPair) {
316307
tokenAddresses.push(CommonAddress.ArbitrumOne.PYUSDOFT);
308+
tokenAddresses.push(CommonAddress.ArbitrumOne.PYUSDCanonical);
317309
}
318310
}
319311
if (isArbitrumSepolia) {
@@ -352,6 +344,15 @@ function TokensPanel({
352344
return tokens
353345
.filter((address) => {
354346
const normalizedAddress = address.toLowerCase();
347+
348+
if (
349+
!isDepositMode &&
350+
isArbitrumOneEthereumPair &&
351+
addressesEqual(address, CommonAddress.Ethereum.PYUSD)
352+
) {
353+
return false;
354+
}
355+
355356
const pyusdToken = getWithdrawalPyusdToken(address);
356357

357358
let token =

packages/arb-token-bridge-ui/src/components/TransferPanel/TokenSearchUtils.test.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,11 @@ describe('tokenListsToSearchableTokenStorage', () => {
5454
'42161',
5555
);
5656

57-
expect(tokens[CommonAddress.Ethereum.PYUSD.toLowerCase()]).toMatchObject({
58-
address: CommonAddress.Ethereum.PYUSD.toLowerCase(),
57+
expect(tokens[CommonAddress.Ethereum.PYUSD]).toMatchObject({
58+
address: CommonAddress.Ethereum.PYUSD,
5959
name: 'PYUSD',
6060
logoURI: 'https://example.com/black.png',
61-
l2Address: CommonAddress.ArbitrumOne.PYUSDOFT.toLowerCase(),
61+
l2Address: CommonAddress.ArbitrumOne.PYUSDOFT,
6262
});
6363
});
6464

@@ -125,11 +125,11 @@ describe('tokenListsToSearchableTokenStorage', () => {
125125
'42161',
126126
);
127127

128-
expect(tokens[CommonAddress.Ethereum.PYUSD.toLowerCase()]).toMatchObject({
129-
address: CommonAddress.Ethereum.PYUSD.toLowerCase(),
128+
expect(tokens[CommonAddress.Ethereum.PYUSD]).toMatchObject({
129+
address: CommonAddress.Ethereum.PYUSD,
130130
name: 'PYUSD',
131131
logoURI: 'https://example.com/black.png',
132-
l2Address: CommonAddress.ArbitrumOne.PYUSDOFT.toLowerCase(),
132+
l2Address: CommonAddress.ArbitrumOne.PYUSDOFT,
133133
});
134134
});
135135
});

packages/arb-token-bridge-ui/src/components/TransferPanel/TransferDisabledDialog.tsx

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { useTokenLists } from '../../hooks/useTokenLists';
1010
import { getL2ConfigForTeleport } from '../../token-bridge-sdk/teleport';
1111
import { ChainId } from '../../types/ChainId';
1212
import { addressesEqual } from '../../util/AddressUtils';
13-
import { isTokenArbitrumOnePyusdOft, isTokenEthereumPyusd } from '../../util/PyusdUtils';
13+
import { isPyusdOverrideFlow } from '../../util/PyusdUtils';
1414
import { isTeleportEnabledToken } from '../../util/TokenTeleportEnabledUtils';
1515
import { isTransferDisabledToken } from '../../util/TokenTransferDisabledUtils';
1616
import { sanitizeTokenSymbol } from '../../util/TokenUtils';
@@ -63,19 +63,10 @@ export function isDisabledCanonicalTransfer({
6363
}
6464

6565
if (
66-
isDepositMode &&
67-
isTokenEthereumPyusd(selectedToken.address) &&
68-
parentChainId === ChainId.Ethereum &&
69-
childChainId === ChainId.ArbitrumOne
70-
) {
71-
return true;
72-
}
73-
74-
if (
75-
!isDepositMode &&
76-
isTokenArbitrumOnePyusdOft(selectedToken.address) &&
77-
childChainId === ChainId.ArbitrumOne &&
78-
parentChainId === ChainId.Ethereum
66+
isPyusdOverrideFlow({
67+
tokenAddress: selectedToken.address,
68+
isDepositMode,
69+
})
7970
) {
8071
return true;
8172
}

packages/arb-token-bridge-ui/src/components/TransferPanel/hooks/useIsSwapTransfer.test.ts

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { constants } from 'ethers';
33
import { DecodedValueMap } from 'use-query-params';
44
import { beforeEach, describe, expect, it, vi } from 'vitest';
55

6+
import { ERC20BridgeToken, TokenType } from '../../../hooks/arbTokenBridge.types';
67
import { queryParamProviderOptions, useArbQueryParams } from '../../../hooks/useArbQueryParams';
78
import { useSelectedToken } from '../../../hooks/useSelectedToken';
89
import { CommonAddress } from '../../../util/CommonAddressUtils';
@@ -42,28 +43,46 @@ vi.mock('../../../hooks/useSelectedToken', () => ({
4243
describe('useIsSwapTransfer', () => {
4344
const mockedUseArbQueryParams = vi.mocked(useArbQueryParams);
4445
const mockedUseSelectedToken = vi.mocked(useSelectedToken);
46+
const defaultSelectedToken: ERC20BridgeToken = {
47+
address: CommonAddress.Ethereum.WETH,
48+
symbol: 'WETH',
49+
name: 'Wrapped Ether',
50+
decimals: 18,
51+
logoURI: 'https://example.com/weth.png',
52+
listIds: new Set(['1']),
53+
type: TokenType.ERC20,
54+
};
4555

4656
beforeEach(() => {
4757
vi.clearAllMocks();
4858

4959
mockedUseArbQueryParams.mockReturnValue([
5060
{
5161
...defaultQueryParams,
52-
destinationToken: CommonAddress.ArbitrumOne.PYUSDOFT,
62+
destinationToken: CommonAddress.Ethereum.WETH,
5363
},
5464
vi.fn(),
5565
]);
5666

67+
mockedUseSelectedToken.mockReturnValue([defaultSelectedToken, vi.fn()]);
68+
});
69+
70+
it('does not treat PYUSD (OFT) to PYUSD (L1) as a swap', () => {
5771
mockedUseSelectedToken.mockReturnValue([
5872
getArbitrumOnePyusdOftToken({
5973
priceUSD: 1,
6074
listIds: new Set(['1']),
6175
}),
6276
vi.fn(),
6377
]);
64-
});
78+
mockedUseArbQueryParams.mockReturnValue([
79+
{
80+
...defaultQueryParams,
81+
destinationToken: CommonAddress.Ethereum.PYUSD,
82+
},
83+
vi.fn(),
84+
]);
6585

66-
it('does not treat PYUSD (OFT) to PYUSD (L1) as a swap', () => {
6786
const { result } = renderHook(useIsSwapTransfer);
6887
expect(result.current).toBe(false);
6988
});
@@ -120,4 +139,31 @@ describe('useIsSwapTransfer', () => {
120139
const { result } = renderHook(useIsSwapTransfer);
121140
expect(result.current).toBe(true);
122141
});
142+
143+
it('returns false when destinationToken is undefined', () => {
144+
mockedUseArbQueryParams.mockReturnValue([
145+
{
146+
...defaultQueryParams,
147+
destinationToken: undefined,
148+
},
149+
vi.fn(),
150+
]);
151+
152+
const { result } = renderHook(useIsSwapTransfer);
153+
expect(result.current).toBe(false);
154+
});
155+
156+
it('returns false when destinationToken is undefined and selectedToken is null', () => {
157+
mockedUseArbQueryParams.mockReturnValue([
158+
{
159+
...defaultQueryParams,
160+
destinationToken: undefined,
161+
},
162+
vi.fn(),
163+
]);
164+
mockedUseSelectedToken.mockReturnValue([null, vi.fn()]);
165+
166+
const { result } = renderHook(useIsSwapTransfer);
167+
expect(result.current).toBe(false);
168+
});
123169
});

packages/arb-token-bridge-ui/src/components/TransferPanel/hooks/useIsSwapTransfer.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,14 @@ export function useIsSwapTransfer() {
66
const [selectedToken] = useSelectedToken();
77
const [{ destinationToken }] = useArbQueryParams();
88

9-
/**
10-
* Destination token is using l1 address and not address on the destination chain
11-
* We can compare both address to know if token is the same without checking l2 address
12-
*/
13-
return !addressesEqual(destinationToken, selectedToken?.address);
9+
if (
10+
!destinationToken ||
11+
addressesEqual(destinationToken, selectedToken?.address) ||
12+
addressesEqual(destinationToken, selectedToken?.importLookupAddress) ||
13+
addressesEqual(destinationToken, selectedToken?.l2Address)
14+
) {
15+
return false;
16+
}
17+
18+
return true;
1419
}

0 commit comments

Comments
 (0)