diff --git a/packages/web/src/hooks/useExternalWalletSwap.ts b/packages/web/src/hooks/useExternalWalletSwap.ts index 2350194bf8c..67f5eb22db8 100644 --- a/packages/web/src/hooks/useExternalWalletSwap.ts +++ b/packages/web/src/hooks/useExternalWalletSwap.ts @@ -1,14 +1,11 @@ -import { - getWalletAudioBalanceQueryKey, - useQueryContext -} from '@audius/common/api' -import { Chain, ErrorLevel, Feature } from '@audius/common/models' +import { useQueryContext } from '@audius/common/api' +import { ErrorLevel, Feature } from '@audius/common/models' import { getExternalWalletBalanceQueryKey } from '@audius/common/src/api/tan-query/wallets/useExternalWalletBalance' import { getJupiterQuoteByMintWithRetry, jupiterInstance } from '@audius/common/src/services/Jupiter' -import { AUDIO, FixedDecimal, type AudioWei } from '@audius/fixed-decimal' +import { FixedDecimal } from '@audius/fixed-decimal' import { SwapRequest } from '@jup-ag/api' import type { Provider as SolanaProvider } from '@reown/appkit-adapter-solana/react' import { VersionedTransaction } from '@solana/web3.js' @@ -80,14 +77,22 @@ export const useExternalWalletSwap = () => { const decoded = Buffer.from(swapTx.swapTransaction, 'base64') const transaction = VersionedTransaction.deserialize(decoded) - const txSignature = - await appKitSolanaProvider.signAndSendTransaction(transaction) + const signedTx = await appKitSolanaProvider.signTransaction(transaction) hookProgress.sentSwapTx = true - await sdk.services.solanaClient.connection.confirmTransaction( - txSignature, - 'confirmed' - ) + const txSignature = + await sdk.services.solanaClient.connection.sendTransaction(signedTx) + + const result = + await sdk.services.solanaClient.connection.confirmTransaction( + txSignature, + 'confirmed' + ) + if (result.value.err) { + throw new Error( + `Transaction confirmed but failed: ${JSON.stringify(result.value.err)}` + ) + } hookProgress.confirmedSwapTx = true return { @@ -176,38 +181,6 @@ export const useExternalWalletSwap = () => { } ) } - - // Handle AUDIO separately since it's stored in a different query hook - if (isSpendingAudio || isReceivingAudio) { - // Update the wallet AUDIO balance based on the swap direction - const queryKey = getWalletAudioBalanceQueryKey({ - address: params.walletAddress, - chain: Chain.Sol - }) - - queryClient.setQueryData( - queryKey, - (oldBalance: AudioWei | undefined) => { - const currentBalance = oldBalance ?? AUDIO(0).value - let newBalance = currentBalance - - // If spending AUDIO (input token), subtract the input amount - if (isSpendingAudio && result.inputAmount) { - const inputAmountAudio = AUDIO(result.inputAmount).value - newBalance = AUDIO(newBalance - inputAmountAudio).value - } - - // If receiving AUDIO (output token), add the output amount - if (isReceivingAudio && result.outputAmount) { - const outputAmountAudio = AUDIO(result.outputAmount).value - newBalance = AUDIO(newBalance + outputAmountAudio).value - } - - // Ensure balance doesn't go negative - return newBalance >= 0 ? newBalance : AUDIO(0).value - } - ) - } } } }) diff --git a/packages/web/src/pages/artist-coins-launchpad-page/components/LaunchpadBuyModal.tsx b/packages/web/src/pages/artist-coins-launchpad-page/components/LaunchpadBuyModal.tsx index ee202645d34..f97cced07d7 100644 --- a/packages/web/src/pages/artist-coins-launchpad-page/components/LaunchpadBuyModal.tsx +++ b/packages/web/src/pages/artist-coins-launchpad-page/components/LaunchpadBuyModal.tsx @@ -1,6 +1,8 @@ import { useContext, useEffect, useMemo, useState } from 'react' +import { useWalletAudioBalance } from '@audius/common/api' import { buySellMessages } from '@audius/common/messages' +import { Chain } from '@audius/common/models' import { useConnectedWallets } from '@audius/common/src/api/tan-query/wallets/useConnectedWallets' import { Name } from '@audius/common/src/models/Analytics' import { TOKEN_LISTING_MAP } from '@audius/common/src/store/ui/buy-audio/constants' @@ -24,6 +26,7 @@ import { TokenAmountInput } from '@audius/harmony' import { FormikProvider, useFormikContext } from 'formik' +import { usePrevious } from 'react-use' import { IconAUDIO } from 'components/buy-audio-modal/components/Icons' import { WALLET_GUIDE_URL } from 'components/buy-sell-modal' @@ -359,6 +362,8 @@ enum BuyModalStep { Loading = 'loading' } +const AUDIO_BALANCE_POLL_INTERVAL = 1000 // short 1s poll interval to check for balance changes + export const LaunchpadBuyModal = ({ isOpen, onClose @@ -378,8 +383,34 @@ export const LaunchpadBuyModal = ({ data: swapData } = useExternalWalletSwap() + const onInputTokenChange = (token: TokenInfo) => { + setSelectedInputToken(token) + } + const { data: connectedWallets } = useConnectedWallets() + const externalWalletAddress = useMemo( + () => getLastConnectedSolWallet(connectedWallets)?.address, + [connectedWallets] + ) + const { data: audioBalance } = useWalletAudioBalance( + { + address: externalWalletAddress!, + chain: Chain.Sol + }, + { refetchInterval: AUDIO_BALANCE_POLL_INTERVAL } + ) + const prevAudioBalance = usePrevious(audioBalance) + + // Due to RPC drift, we have to wait for the audio balance hook to show our changes + // This is because the audio balance hook is polling, so an optimistic change here is not sufficient + const hasAudioBalanceChanged = useMemo(() => { + return ( + prevAudioBalance && + audioBalance?.toString() !== prevAudioBalance?.toString() + ) + }, [audioBalance, prevAudioBalance]) + useEffect(() => { - if (swapSuccess) { + if (swapSuccess && hasAudioBalanceChanged) { track(make({ eventName: Name.LAUNCHPAD_BUY_MODAL_SUCCESS })) setCurrentStep(BuyModalStep.Success) } @@ -397,16 +428,14 @@ export const LaunchpadBuyModal = ({ toast(toastMessage, 5000) setCurrentStep(BuyModalStep.Form) } - }, [swapSuccess, swapPending, swapError, toast, swapData]) - - const onInputTokenChange = (token: TokenInfo) => { - setSelectedInputToken(token) - } - const { data: connectedWallets } = useConnectedWallets() - const externalWalletAddress = useMemo( - () => getLastConnectedSolWallet(connectedWallets)?.address, - [connectedWallets] - ) + }, [ + swapSuccess, + swapPending, + swapError, + toast, + swapData, + hasAudioBalanceChanged + ]) const { availableBalance, isBalanceLoading,