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
95 changes: 43 additions & 52 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/common/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
"@audius/fixed-decimal": "*",
"@audius/sdk": "*",
"@fingerprintjs/fingerprintjs-pro": "3.5.6",
"@jup-ag/api": "6.0.44",
"@jup-ag/api": "6.0.48",
"@metaplex-foundation/mpl-token-metadata": "2.5.2",
"@optimizely/optimizely-sdk": "4.0.0",
"@tanstack/react-query": "5.62.7",
Expand Down
1 change: 1 addition & 0 deletions packages/common/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ export * from './tan-query/wallets/useAudioBalance'
export * from './tan-query/wallets/useAssociatedWallets'
export * from './tan-query/wallets/useWalletOwner'
export * from './tan-query/wallets/useUSDCBalance'
export * from './tan-query/wallets/useDestinationUsdcAccountCheck'
export * from './tan-query/wallets/useExternalWalletBalance'
export * from './tan-query/wallets/useCoinBalance'
export * from './tan-query/wallets/useCoinBalanceBreakdown'
Expand Down
1 change: 1 addition & 0 deletions packages/common/src/api/tan-query/queryKeys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ export const QUERY_KEYS = {
walletOwner: 'walletOwner',
tokenPrice: 'tokenPrice',
usdcBalance: 'usdcBalance',
destinationUsdcAccount: 'destinationUsdcAccount',
fileSizes: 'fileSizes',
sendTokens: 'sendTokens',
managedAccounts: 'managedAccounts',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { getAssociatedTokenAddressSync } from '@solana/spl-token'
import { PublicKey } from '@solana/web3.js'
import { useQuery } from '@tanstack/react-query'

import { SolanaWalletAddress } from '~/models/Wallet'
import { getJupiterQuoteByMintWithRetry } from '~/services/Jupiter'
import { SOL_MINT, TOKEN_LISTING_MAP } from '~/store/ui/shared/tokenConstants'
import { isValidSolAddress } from '~/store/wallet/utils'

import { QUERY_KEYS } from '../queryKeys'
import type { QueryKey, QueryOptions } from '../types'
import { useQueryContext } from '../utils'

/** Result of checking whether a destination wallet has a USDC token account */
export type DestinationUsdcAccountStatus =
| { hasUsdcAccount: true }
| { hasUsdcAccount: false; ataCreationFeeUsdc: number }

const USDC_DECIMALS = TOKEN_LISTING_MAP.USDC.decimals
const SOL_DECIMALS = TOKEN_LISTING_MAP.SOL.decimals
const TOKEN_ACCOUNT_SIZE = 165
const ATA_TX_FEE_BUFFER_LAMPORTS = 10_000

export const getDestinationUsdcAccountQueryKey = (
destinationAddress: string | null | undefined
) =>
[
QUERY_KEYS.destinationUsdcAccount,
destinationAddress
] as unknown as QueryKey<DestinationUsdcAccountStatus>

/**
* Checks whether a destination Solana address has a USDC token account.
* When it doesn't, returns the estimated one-time fee to create it.
* Use when the user pastes a destination address during USDC withdrawal (wallet route).
*/
export const useDestinationUsdcAccountCheck = (
destinationAddress: string | null | undefined,
options?: QueryOptions<DestinationUsdcAccountStatus>
) => {
const { audiusSdk, env } = useQueryContext()
const isValidAddress =
!!destinationAddress &&
isValidSolAddress(destinationAddress as SolanaWalletAddress)

return useQuery<DestinationUsdcAccountStatus>({
queryKey: getDestinationUsdcAccountQueryKey(destinationAddress),
queryFn: async () => {
const sdk = await audiusSdk()
const connection = sdk.services.solanaClient.connection
const mint = new PublicKey(env.USDC_MINT_ADDRESS)
const destinationWallet = new PublicKey(destinationAddress!)

const destinationAta = getAssociatedTokenAddressSync(
mint,
destinationWallet,
true
)

const info = await connection.getAccountInfo(destinationAta)
if (info) return { hasUsdcAccount: true }

const rentExemptLamports =
await connection.getMinimumBalanceForRentExemption(TOKEN_ACCOUNT_SIZE)
const totalSolNeededLamports =
rentExemptLamports + ATA_TX_FEE_BUFFER_LAMPORTS
const totalSolNeededUi = totalSolNeededLamports / 1e9

const { quoteResult: costQuote } = await getJupiterQuoteByMintWithRetry({
inputMint: mint.toBase58(),
outputMint: SOL_MINT,
inputDecimals: USDC_DECIMALS,
outputDecimals: SOL_DECIMALS,
amountUi: totalSolNeededUi,
swapMode: 'ExactOut',
onlyDirectRoutes: false
})

const ataCreationFeeUsdc =
Number(BigInt(costQuote.inputAmount.amountString)) / 10 ** USDC_DECIMALS

return { hasUsdcAccount: false, ataCreationFeeUsdc }
},
enabled: isValidAddress && options?.enabled !== false,
staleTime: 60_000,
...options
})
}
6 changes: 5 additions & 1 deletion packages/common/src/api/tan-query/wallets/useSendCoins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,11 @@ export const useSendCoins = ({ mint }: { mint: string }) => {
}
}
},
onError: (error, { amount, recipientWallet, source, recipientHandle }, context) => {
onError: (
error,
{ amount, recipientWallet, source, recipientHandle },
context
) => {
if (context?.previousBalance) {
const userId = currentUser?.user_id ?? null
const queryKey = getUserCoinQueryKey(mint, userId)
Expand Down
6 changes: 6 additions & 0 deletions packages/common/src/messages/walletMessages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,12 @@ export const walletMessages = {
amountTooLow: 'Amount must be greater than zero.',
invalidAddress: 'A valid Solana USDC wallet address is required',
minCashTransfer: 'A minimum of $5 is required for cash withdrawals.',
ataCreationFeeRequired: (feeDollars: string) =>
`Amount must cover the one-time account creation fee of $${feeDollars}`,
noUsdcAccountFound: (feeDollars?: string) =>
feeDollars != null
? `No USDC account found. A one-time fee of $${feeDollars} will be deducted.`
: 'No USDC account found',
pleaseConfirm:
'Please confirm you have reviewed this transaction and accept responsibility for errors.',
youMustConfirm:
Expand Down
Loading
Loading