diff --git a/packages/web/src/components/payout-wallet-modal/PayoutWalletModal.tsx b/packages/web/src/components/payout-wallet-modal/PayoutWalletModal.tsx index 0c899adf21a..8cd2c7a9e3f 100644 --- a/packages/web/src/components/payout-wallet-modal/PayoutWalletModal.tsx +++ b/packages/web/src/components/payout-wallet-modal/PayoutWalletModal.tsx @@ -19,13 +19,19 @@ import { } from '@audius/harmony' import { Formik, useField } from 'formik' import { useDispatch, useSelector } from 'react-redux' +import { useAsync } from 'react-use' import { z } from 'zod' import { toFormikValidationSchema } from 'zod-formik-adapter' import { useModalState } from 'common/hooks/useModalState' import { TextField } from 'components/form-fields' import { ModalForm } from 'components/modal-form/ModalForm' -import { isValidSolAddress } from 'services/solana/solana' +import { + getAssociatedTokenAccountOwner, + isTokenAccount, + isValidSolAddress +} from 'services/solana/solana' +import { reportToSentry } from 'store/errors/reportToSentry' const { getAccountUser } = accountSelectors @@ -148,9 +154,24 @@ export const PayoutWalletModal = () => { }, [setIsOpen]) const handleSubmit = useCallback( - ({ option, address }: PayoutWalletValues) => { - // TODO: Token account validation - if (user) { + async ( + { option, address }: PayoutWalletValues, + { setErrors }: { setErrors: any } + ) => { + try { + if (!address || !user) { + throw new Error('No user or address') + } + + const isUsdcAta = await isTokenAccount({ + accountAddress: address as SolanaWalletAddress, + mint: 'usdc' + }) + if (!isUsdcAta) { + // Create ATA via relay + throw new Error('Create ATA not implemented') + } + const updatedUser = { ...user } if (option === 'default') { updatedUser.spl_usdc_payout_wallet = null @@ -158,16 +179,32 @@ export const PayoutWalletModal = () => { updatedUser.spl_usdc_payout_wallet = address as SolanaWalletAddress } dispatch(profilePageActions.updateProfile(updatedUser)) + setIsOpen(false) + } catch (e) { + setErrors({ address: 'Please try again later' }) + await reportToSentry({ + error: e as Error, + name: 'Payout Wallet: Error setting wallet' + }) } - setIsOpen(false) }, [dispatch, user, setIsOpen] ) + const { value: payoutWallet } = useAsync(async () => { + if (user?.spl_usdc_payout_wallet) { + const owner = await getAssociatedTokenAccountOwner( + user.spl_usdc_payout_wallet + ) + return owner.toString() + } + return null + }, [user]) + const initialValues: PayoutWalletValues = user?.spl_usdc_payout_wallet ? { option: 'custom', - address: user.spl_usdc_payout_wallet as string + address: payoutWallet ?? '' } : { option: 'default' } @@ -177,6 +214,7 @@ export const PayoutWalletModal = () => { } /> { setIsOpen(true) }, [setIsOpen]) + const { value: payoutWallet } = useAsync(async () => { + if (user?.spl_usdc_payout_wallet) { + const owner = await getAssociatedTokenAccountOwner( + user.spl_usdc_payout_wallet + ) + return owner.toBase58() + } + return null + }, [user]) + return ( @@ -58,8 +71,8 @@ export const PayoutWalletCard = () => { )} - {user?.spl_usdc_payout_wallet - ? shortenSPLAddress(user?.spl_usdc_payout_wallet) + {payoutWallet + ? shortenSPLAddress(payoutWallet) : messages.audiusWallet} diff --git a/packages/web/src/services/solana/solana.ts b/packages/web/src/services/solana/solana.ts index 6e9daf8aa24..d74f65817bf 100644 --- a/packages/web/src/services/solana/solana.ts +++ b/packages/web/src/services/solana/solana.ts @@ -3,7 +3,8 @@ import { DEFAULT_MINT, MintName } from '@audius/common/services' import { Account, getMinimumBalanceForRentExemptAccount, - getAssociatedTokenAddressSync + getAssociatedTokenAddressSync, + getAccount } from '@solana/spl-token' import { PublicKey, Transaction, Keypair } from '@solana/web3.js' @@ -143,6 +144,27 @@ export const getUSDCAssociatedTokenAccount = async ( ) } +/** + * Returns the owner of the token acccount, if the provided account + * is a token account. Otherwise, just returns the account + */ +export const getAssociatedTokenAccountOwner = async ( + accountAddress: SolanaWalletAddress +) => { + const connection = await getSolanaConnection() + try { + const { owner } = await getAccount( + connection, + new PublicKey(accountAddress) + ) + return owner + } catch (e) { + // Not a token account, so return the provided address + console.error(e) + return new PublicKey(accountAddress) + } +} + /** * Returns the current user's USDC user bank. */