diff --git a/wormhole-connect/src/config/index.ts b/wormhole-connect/src/config/index.ts index 70b6991d5..80703f184 100644 --- a/wormhole-connect/src/config/index.ts +++ b/wormhole-connect/src/config/index.ts @@ -21,10 +21,6 @@ import { } from '@wormhole-foundation/sdk'; import '@wormhole-foundation/sdk/addresses'; -import evm from '@wormhole-foundation/sdk/evm'; -import solana from '@wormhole-foundation/sdk/solana'; -import aptos from '@wormhole-foundation/sdk/aptos'; -import sui from '@wormhole-foundation/sdk/sui'; import RouteOperator from 'routes/operator'; import { CHAIN_ORDER } from './constants'; import { createUiConfig } from './ui'; @@ -192,6 +188,11 @@ export async function newWormholeContextV2(): Promise> { v2Config.chains![chain] = { rpc, tokenMap }; } + const { default: evm } = await import('@wormhole-foundation/sdk/evm'); + const { default: solana } = await import('@wormhole-foundation/sdk/solana'); + const { default: aptos } = await import('@wormhole-foundation/sdk/aptos'); + const { default: sui } = await import('@wormhole-foundation/sdk/sui'); + return await getWormholeV2( config.network, [evm, solana, aptos, sui], diff --git a/wormhole-connect/src/utils/sdkv2.ts b/wormhole-connect/src/utils/sdkv2.ts index 7a5e0b463..10aba6a45 100644 --- a/wormhole-connect/src/utils/sdkv2.ts +++ b/wormhole-connect/src/utils/sdkv2.ts @@ -19,9 +19,6 @@ import { } from '@wormhole-foundation/sdk'; import config from 'config'; import { NttRoute } from '@wormhole-foundation/sdk-route-ntt'; -import { Connection } from '@solana/web3.js'; -import { PublicKey } from '@solana/web3.js'; -import * as splToken from '@solana/spl-token'; import { WORMSCAN } from 'config/constants'; import { TokenTuple } from 'config/tokens'; @@ -185,18 +182,12 @@ const parseTokenBridgeReceipt = async ( if (payload.to) { if (receipt.to === 'Solana') { - if (!config.rpcs.Solana) { - throw new Error('Missing Solana RPC'); - } // the recipient on the VAA is the ATA const ata = payload.to.address.toNative(receipt.to).toString(); - const connection = new Connection(config.rpcs.Solana); try { - const account = await splToken.getAccount( - connection, - new PublicKey(ata), - ); - txData.recipient = account.owner.toBase58(); + const { getAtaOwnerAddress } = await import('utils/solana'); + const owner = await getAtaOwnerAddress(ata); + txData.recipient = owner; } catch (e) { console.error(e); txData.recipient = ata; @@ -256,10 +247,10 @@ const parseCCTPReceipt = async ( } // the recipient on the VAA is the ATA const ata = payload.mintRecipient.toNative(receipt.to).toString(); - const connection = new Connection(config.rpcs.Solana); try { - const account = await splToken.getAccount(connection, new PublicKey(ata)); - txData.recipient = account.owner.toBase58(); + const { getAtaOwnerAddress } = await import('utils/solana'); + const owner = await getAtaOwnerAddress(ata); + txData.recipient = owner; } catch (e) { console.error(e); txData.recipient = ata; diff --git a/wormhole-connect/src/utils/solana.ts b/wormhole-connect/src/utils/solana.ts index 2858585f5..1336e2224 100644 --- a/wormhole-connect/src/utils/solana.ts +++ b/wormhole-connect/src/utils/solana.ts @@ -9,7 +9,13 @@ import { Commitment, SimulatedTransactionResponse, LAMPORTS_PER_SOL, + PublicKey, } from '@solana/web3.js'; +import { + getAccount as getSplAccount, + getAssociatedTokenAddressSync, + NATIVE_MINT, +} from '@solana/spl-token'; import { determinePriorityFee, @@ -18,7 +24,7 @@ import { SolanaUnsignedTransaction, } from '@wormhole-foundation/sdk-solana'; -import { Network } from '@wormhole-foundation/sdk'; +import { isNative, Network } from '@wormhole-foundation/sdk'; import { isEmptyObject, sleep } from 'utils'; import config from 'config'; @@ -289,3 +295,24 @@ function checkKnownSimulationError( console.table(errors); return true; } + +export function getAtaAddress( + walletAddress: string, + tokenAddress: string, +): string { + return getAssociatedTokenAddressSync( + new PublicKey( + isNative(tokenAddress) ? NATIVE_MINT : tokenAddress.toString(), + ), + new PublicKey(walletAddress), + ).toString(); +} + +export async function getAtaOwnerAddress(ataAddress: string): Promise { + if (!config.rpcs.Solana) { + throw new Error('Missing Solana RPC'); + } + const connection = new Connection(config.rpcs.Solana); + const account = await getSplAccount(connection, new PublicKey(ataAddress)); + return account.owner.toBase58(); +} diff --git a/wormhole-connect/src/utils/wallet/index.ts b/wormhole-connect/src/utils/wallet/index.ts index 7fde30251..04ab2f182 100644 --- a/wormhole-connect/src/utils/wallet/index.ts +++ b/wormhole-connect/src/utils/wallet/index.ts @@ -23,10 +23,6 @@ import { SuiUnsignedTransaction, SuiChains, } from '@wormhole-foundation/sdk-sui'; -import { - AptosUnsignedTransaction, - AptosChains, -} from '@wormhole-foundation/sdk-aptos'; import { SolanaUnsignedTransaction } from '@wormhole-foundation/sdk-solana'; import { ReadOnlyWallet } from './ReadOnlyWallet'; @@ -254,10 +250,7 @@ export const signAndSendTransaction = async ( return tx.id; } else if (chainConfig.context === Context.APTOS) { const aptos = await import('utils/wallet/aptos'); - const tx = await aptos.signAndSendTransaction( - request as AptosUnsignedTransaction, - wallet, - ); + const tx = await aptos.signAndSendTransaction(request as any, wallet); return tx.id; } else { throw new Error('unimplemented'); diff --git a/wormhole-connect/src/views/v2/Bridge/WalletConnector/Sidebar.tsx b/wormhole-connect/src/views/v2/Bridge/WalletConnector/Sidebar.tsx index 0b227f4d0..f4c876c57 100644 --- a/wormhole-connect/src/views/v2/Bridge/WalletConnector/Sidebar.tsx +++ b/wormhole-connect/src/views/v2/Bridge/WalletConnector/Sidebar.tsx @@ -24,7 +24,6 @@ import { TransferWallet, WalletData, connectWallet } from 'utils/wallet'; import AlertBannerV2 from 'components/v2/AlertBanner'; import { useAvailableWallets } from 'hooks/useAvailableWallets'; import WalletIcon from 'icons/WalletIcons'; -import { validateWalletAddress } from 'utils/address'; import { ReadOnlyWallet } from 'utils/wallet/ReadOnlyWallet'; import { SANCTIONED_WALLETS } from 'consts/wallet'; @@ -122,6 +121,7 @@ const WalletSidebar = (props: Props) => { const chainConfig = config.chains[selectedChain]; if (!chainConfig) return; + const { validateWalletAddress } = await import('utils/address'); const nativeAddress = await validateWalletAddress(selectedChain, address); if (!nativeAddress) { setAddressError('Invalid Address'); diff --git a/wormhole-connect/src/views/v2/Redeem/index.tsx b/wormhole-connect/src/views/v2/Redeem/index.tsx index 85f931dd9..1f9f29977 100644 --- a/wormhole-connect/src/views/v2/Redeem/index.tsx +++ b/wormhole-connect/src/views/v2/Redeem/index.tsx @@ -21,7 +21,6 @@ import { isRefunded, isFailed, routes, - isNative, } from '@wormhole-foundation/sdk'; import { getTokenDetails, getTransferDetails } from 'telemetry'; import { makeStyles } from 'tss-react/mui'; @@ -57,8 +56,6 @@ import type { RootState } from 'store'; import TxCompleteIcon from 'icons/TxComplete'; import TxWarningIcon from 'icons/TxWarning'; import TxFailedIcon from 'icons/TxFailed'; -import { getAssociatedTokenAddressSync, NATIVE_MINT } from '@solana/spl-token'; -import { PublicKey } from '@solana/web3.js'; import TxReadyForClaim from 'icons/TxReadyForClaim'; import { useGetRedeemTokens } from 'hooks/useGetTokens'; import { tokenIdFromTuple } from 'config/tokens'; @@ -138,6 +135,8 @@ const Redeem = () => { const [claimError, setClaimError] = useState(''); const [isClaimInProgress, setIsClaimInProgress] = useState(false); + const [isConnectedToReceivingWallet, setIsConnectedToReceivingWallet] = + useState(false); const [transferSuccessEventFired, setTransferSuccessEventFired] = useState(false); const [etaExpired, setEtaExpired] = useState(false); @@ -639,53 +638,62 @@ const Redeem = () => { }, [routeContext.receipt]); // Checks whether the receiving wallet is currently connected - const isConnectedToReceivingWallet = useMemo(() => { + useEffect(() => { + let canceled = false; + const rv = () => { + canceled = true; + }; + if (!recipient) { - return false; + return rv; } - // For Solana transfers, the associated token account (ATA) might not exist, - // preventing us from retrieving the recipient wallet address. - // In such cases, when resuming transfers, we allow the user to connect a wallet - // to claim the transfer, which will create the ATA. - if ( - isResumeTx && - toChain === 'Solana' && - receivingWallet.address && - receivingWallet.type === Context.SOLANA && - receivingWallet.address !== recipient && - routeName && - // These routes set the recipient address to the associated token address - ['ManualTokenBridge', 'ManualCCTP'].includes(routeName) - ) { - const { address: receiveTokenAddress } = tokenIdFromTuple(receivedToken); - - const ata = getAssociatedTokenAddressSync( - new PublicKey( - isNative(receiveTokenAddress) - ? NATIVE_MINT - : receiveTokenAddress.toString(), - ), - new PublicKey(receivingWallet.address), - ); - if (!ata.equals(new PublicKey(recipient))) { - setClaimError('Not connected to the receiving wallet'); - return false; + const check = async () => { + if ( + isResumeTx && + toChain === 'Solana' && + receivingWallet.address && + receivingWallet.type === Context.SOLANA && + receivingWallet.address !== recipient && + routeName && + ['ManualTokenBridge', 'ManualCCTP'].includes(routeName) + ) { + const { address: receiveTokenAddress } = + tokenIdFromTuple(receivedToken); + + const { getAtaAddress } = await import('utils/solana'); + const ata = getAtaAddress( + receivingWallet.address, + receiveTokenAddress.toString(), + ); + if (ata != recipient) { + if (!canceled) { + setClaimError('Not connected to the receiving wallet'); + setIsConnectedToReceivingWallet(false); + } + } + + if (!canceled) { + setClaimError(''); + setIsConnectedToReceivingWallet(true); + } } - setClaimError(''); - return true; - } + const walletAddress = receivingWallet.address.toLowerCase(); + const walletCurrentAddress = receivingWallet.currentAddress.toLowerCase(); + const recipientAddress = recipient.toLowerCase(); + + if (!canceled) { + setIsConnectedToReceivingWallet( + walletAddress === walletCurrentAddress && + walletAddress === recipientAddress, + ); + } + }; - const walletAddress = receivingWallet.address.toLowerCase(); - const walletCurrentAddress = receivingWallet.currentAddress.toLowerCase(); - const recipientAddress = recipient.toLowerCase(); + check(); - // Connected wallet should be the current recipient wallet - return ( - walletAddress === walletCurrentAddress && - walletAddress === recipientAddress - ); + return rv; }, [ receivingWallet, recipient,