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
4 changes: 4 additions & 0 deletions packages/app/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ module.exports = {
externalDir: true,
},
webpack: (config) => {
config.resolve.alias = {
...config.resolve.alias,
'@react-native-async-storage/async-storage': false,
};
config.externals.push('pino-pretty', 'lokijs', 'encoding', '@duneanalytics/client-sdk');
// pnpm's strict isolation can cause packages to resolve their own copy
// of context-dependent libraries, breaking React context sharing.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { ArbQueryParamProvider } from '@/bridge/hooks/useArbQueryParams';
import { isE2eTestingEnvironment, isProductionEnvironment } from '@/bridge/util/CommonUtils';
import { registerLocalNetwork } from '@/bridge/util/networks';
import { wagmiConfig } from '@/bridge/util/wagmi/setup';
import { BalanceProvider } from '@/bridge/wallet/providers/BalanceProvider';
import { WalletProvider } from '@/bridge/wallet/providers/WalletProvider';

import { initializeDayjs } from '../../../initialization';
Expand Down Expand Up @@ -52,7 +53,11 @@ export function AppProviders({ children }: PropsWithChildren) {
<ArbQueryParamProvider>
<WagmiProvider config={wagmiConfig}>
<QueryClientProvider client={queryClient}>
<AppContextProvider>{children}</AppContextProvider>
<WalletProvider>
<BalanceProvider>
<AppContextProvider>{children}</AppContextProvider>
</BalanceProvider>
</WalletProvider>
</QueryClientProvider>
</WagmiProvider>
</ArbQueryParamProvider>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,19 @@ export const lifiDestinationChainIds: Record<number, number[]> = {
ChainId.ArbitrumNova,
ChainId.ApeChain,
ChainId.Superposition,
ChainId.Solana,
],
[ChainId.ArbitrumOne]: [
ChainId.Ethereum,
ChainId.ApeChain,
ChainId.Superposition,
ChainId.Solana,
],
[ChainId.ArbitrumOne]: [ChainId.Ethereum, ChainId.ApeChain, ChainId.Superposition],
[ChainId.ArbitrumNova]: [ChainId.Ethereum, ChainId.ArbitrumOne],
[ChainId.ApeChain]: [ChainId.Ethereum, ChainId.ArbitrumOne, ChainId.Superposition],
[ChainId.Superposition]: [ChainId.Ethereum, ChainId.ArbitrumOne, ChainId.ApeChain],
[ChainId.Base]: [ChainId.ArbitrumOne, ChainId.ApeChain, ChainId.Superposition],
[ChainId.Solana]: [ChainId.ArbitrumOne, ChainId.Ethereum],
};

export const allowedLifiSourceChainIds: number[] = Object.keys(lifiDestinationChainIds).map((id) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
StepToolDetails,
createConfig,
getRoutes,
isSVMAddress,
} from '@lifi/sdk';
import { BigNumber, constants, utils } from 'ethers';
import { NextRequest, NextResponse } from 'next/server';
Expand All @@ -18,7 +19,7 @@ import { APE_TOKEN_LOGO, ETHER_TOKEN_LOGO, ether } from '../../../constants';
import { ChainId } from '../../../types/ChainId';
import { addressesEqual } from '../../../util/AddressUtils';
import { CrosschainTransfersRouteBase, QueryParams, Token } from './types';
import { isValidLifiTransfer } from './utils';
import { isValidLifiTransfer, solanaNativeTokenAddress } from './utils';

export const LIFI_INTEGRATOR_IDS = {
NORMAL: '_arbitrum',
Expand Down Expand Up @@ -356,14 +357,24 @@ export async function GET(

try {
// Validate parameters
if (!fromToken || !utils.isAddress(fromToken)) {
const isValidFromTokenAddress =
Number(fromChainId) === ChainId.Solana
? fromToken === solanaNativeTokenAddress || (fromToken ? isSVMAddress(fromToken) : false)
: !!fromToken && utils.isAddress(fromToken);

if (!isValidFromTokenAddress) {
return NextResponse.json(
{ message: 'fromToken is not a valid address', data: null },
{ status: 400 },
);
}

if (!toToken || !utils.isAddress(toToken)) {
const isValidToTokenAddress =
Number(toChainId) === ChainId.Solana
? toToken === solanaNativeTokenAddress || (toToken ? isSVMAddress(toToken) : false)
: !!toToken && utils.isAddress(toToken);

if (!isValidToTokenAddress) {
return NextResponse.json(
{ message: 'toToken is not a valid address', data: null },
{ status: 400 },
Expand All @@ -380,7 +391,7 @@ export async function GET(

if (
!isValidLifiTransfer({
fromToken,
fromToken: fromToken ?? undefined,
sourceChainId: Number(fromChainId),
destinationChainId: Number(toChainId),
})
Expand All @@ -406,13 +417,16 @@ export async function GET(
);
}

const normalizedFromToken = fromToken ?? '';
const normalizedToToken = toToken ?? '';

const parameters: RoutesRequest = {
fromAddress,
fromAmount,
fromTokenAddress: fromToken,
fromTokenAddress: normalizedFromToken,
fromChainId: Number(fromChainId),
toChainId: Number(toChainId),
toTokenAddress: toToken,
toTokenAddress: normalizedToToken,
toAddress,
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ import { addressesEqual } from '../../../util/AddressUtils';
import { CommonAddress, bridgedUsdcToken, commonUsdcToken } from '../../../util/CommonAddressUtils';
import { allowedLifiSourceChainIds, lifiDestinationChainIds } from './constants';

export const solanaNativeTokenAddress = '11111111111111111111111111111111';
export const solanaUsdcTokenAddress = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v';
export const solanaUsdtTokenAddress = 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB';

export function isLifiTransfer({
sourceChainId,
destinationChainId,
Expand Down Expand Up @@ -59,6 +63,10 @@ export function isValidLifiTransfer({
return true;
}

if (sourceChainId === ChainId.Solana) {
return fromToken === solanaNativeTokenAddress;
}

if (!tokensFromLists) {
return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { DisabledFeatures, useArbQueryParams } from '../../hooks/useArbQueryPara
import { useDisabledFeatures } from '../../hooks/useDisabledFeatures';
import { sanitizeQueryParams } from '../../hooks/useNetworks';
import { getAccountType } from '../../util/AccountUtils';
import { isSolanaEnabled } from '../../util/featureFlag';
import { getNetworkName } from '../../util/networks';

export function useSyncConnectedChainToQueryParams() {
Expand All @@ -25,6 +26,8 @@ export function useSyncConnectedChainToQueryParams() {
DisabledFeatures.TRANSFERS_TO_NON_ARBITRUM_CHAINS,
);

const shouldDisableConnectedChainSync = isSolanaEnabled();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

How is this going to work once the feature is shipped? Is it gonna break existing behavior for EVM chains?


const setSourceChainToConnectedChain = useCallback(() => {
if (!chain) {
return;
Expand All @@ -41,45 +44,72 @@ export function useSyncConnectedChainToQueryParams() {
}, [chain, setQueryParams, disableTransfersToNonArbitrumChains]);

useEffect(() => {
if (shouldDisableConnectedChainSync) {
return;
}

if (!chain || sourceChain === undefined || accountType !== 'smart-contract-wallet') {
return;
}

if (sourceChain !== chain.id) {
setSourceChainToConnectedChain();
}
}, [accountType, chain, setSourceChainToConnectedChain, sourceChain]);
}, [
accountType,
chain,
setSourceChainToConnectedChain,
shouldDisableConnectedChainSync,
sourceChain,
]);

useEffect(() => {
if (shouldDisableConnectedChainSync || typeof chain === 'undefined' || !address) {
return;
}

if (sourceChain === chain.id) {
return;
}

const connectedAddress = address;
const connectedChain = chain;

async function checkCorrectChainForSmartContractWallet() {
if (typeof chain === 'undefined') {
return;
}
if (!address) {
return;
}
const accountType = await getAccountType({
address: address,
chainId: chain.id,
const connectedAccountType = await getAccountType({
address: connectedAddress,
chainId: connectedChain.id,
});
if (accountType === 'smart-contract-wallet' && sourceChain !== chain.id) {
const chainName = getNetworkName(chain.id);

setSourceChainToConnectedChain();

window.alert(
`You're connected to the app with a smart contract wallet on ${chainName}. In order to properly enable transfers, the app will now reload.\n\nPlease reconnect after the reload.`,
);
await disconnect({ namespace: 'eip155' });
if (connectedAccountType !== 'smart-contract-wallet') {
return;
}
}

if (sourceChain !== chain?.id) {
const chainName = getNetworkName(connectedChain.id);

setSourceChainToConnectedChain();

window.alert(
`You're connected to the app with a smart contract wallet on ${chainName}. In order to properly enable transfers, the app will now reload.\n\nPlease reconnect after the reload.`,
);
await disconnect({ namespace: 'eip155' });
}
}, [accountType, chain, setSourceChainToConnectedChain, sourceChain]);

void checkCorrectChainForSmartContractWallet();
}, [
address,
chain,
disconnect,
setSourceChainToConnectedChain,
shouldDisableConnectedChainSync,
sourceChain,
]);

useEffect(() => {
if (shouldDisableConnectedChainSync) {
return;
}

if (shouldSync) {
return;
}
Expand All @@ -88,13 +118,17 @@ export function useSyncConnectedChainToQueryParams() {
if (sourceChain === undefined && destinationChain === undefined) {
setShouldSync(true);
}
}, [shouldSync, sourceChain, destinationChain]);
}, [destinationChain, shouldDisableConnectedChainSync, shouldSync, sourceChain]);

useEffect(() => {
if (shouldDisableConnectedChainSync) {
return;
}

// When the chain is connected and we should sync, and we haven't synced yet, sync the connected chain to the query params
if (chain && shouldSync && !didSync) {
setSourceChainToConnectedChain();
setDidSync(true);
}
}, [chain, shouldSync, didSync, setSourceChainToConnectedChain]);
}, [chain, didSync, setSourceChainToConnectedChain, shouldDisableConnectedChainSync, shouldSync]);
}
Loading
Loading