diff --git a/apps/web/src/app/features/account/account-data-access.tsx b/apps/web/src/app/features/account/account-data-access.tsx
index fa10dee..db3176e 100644
--- a/apps/web/src/app/features/account/account-data-access.tsx
+++ b/apps/web/src/app/features/account/account-data-access.tsx
@@ -10,126 +10,159 @@ import {
TransactionSignature,
VersionedTransaction,
} from '@solana/web3.js'
-import { useMutation, useQuery } from '@tanstack/react-query'
+import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { useCluster } from '../cluster/cluster-data-access'
+import { NotificationData } from '@mantine/notifications'
+import { Anchor } from '@mantine/core'
-export function transactionToast(signature: string) {
- toastSuccess({
- title: 'Transaction sent',
- message: `
-
`,
- })
-}
-
-export function useAccount({ address }: { address: PublicKey }) {
- const { cluster } = useCluster()
+export function useQueries({ address }: { address: PublicKey }) {
const { connection } = useConnection()
-
const wallet = useWallet()
- const getBalance = useQuery({
- queryKey: ['balance', { cluster, address }],
- queryFn: () => connection.getBalance(address),
- })
-
- const getSignatures = useQuery({
- queryKey: ['signatures', { cluster, address }],
- queryFn: () => connection.getConfirmedSignaturesForAddress2(address),
- })
-
- const getTokenAccounts = useQuery({
- queryKey: ['token-accounts', { endpoint: connection.rpcEndpoint, address: address.toString() }],
- queryFn: async () => {
- const [tokenAccounts, token2022Accounts] = await Promise.all([
- connection.getParsedTokenAccountsByOwner(address, {
- programId: TOKEN_PROGRAM_ID,
- }),
- connection.getParsedTokenAccountsByOwner(address, {
- programId: TOKEN_2022_PROGRAM_ID,
- }),
- ])
- return [...tokenAccounts.value, ...token2022Accounts.value]
+ return {
+ getBalance: {
+ queryKey: ['getBalance', { endpoint: connection?.rpcEndpoint, address }],
+ queryFn: () => connection.getBalance(address),
},
- })
-
- const getTokenBalance = useQuery({
- queryKey: ['getTokenAccountBalance', { endpoint: connection.rpcEndpoint, account: address.toString() }],
- queryFn: () => connection.getTokenAccountBalance(address),
- })
-
- const requestAirdrop = useMutation({
- mutationKey: ['airdrop', { cluster, address }],
- mutationFn: async (amount: number = 1) => {
- const [latestBlockhash, signature] = await Promise.all([
- connection.getLatestBlockhash(),
- connection.requestAirdrop(address, amount * LAMPORTS_PER_SOL),
- ])
-
- await connection.confirmTransaction({ signature, ...latestBlockhash }, 'confirmed')
- return signature
+ getSignatures: {
+ queryKey: ['getSignatures', { endpoint: connection?.rpcEndpoint, address }],
+ queryFn: () => connection.getConfirmedSignaturesForAddress2(address),
},
- onSuccess: (signature) => {
- transactionToast(signature)
- return Promise.all([getBalance.refetch(), getSignatures.refetch()])
+ getTokenAccounts: {
+ queryKey: ['getTokenAccounts', { endpoint: connection?.rpcEndpoint, address }],
+ queryFn: () => getAllTokenAccounts(connection, address),
},
- })
-
- const transferSol = useMutation({
- mutationKey: ['transfer-sol', { cluster, address }],
- mutationFn: async (input: { destination: PublicKey; amount: number }) => {
- let signature: TransactionSignature = ''
- try {
- const { transaction, latestBlockhash } = await createTransaction({
- publicKey: address,
- destination: input.destination,
- amount: input.amount,
- connection,
- })
-
- // Send transaction and await for signature
- signature = await wallet.sendTransaction(transaction, connection)
-
- // Send transaction and await for signature
- await connection.confirmTransaction({ signature, ...latestBlockhash }, 'confirmed')
-
- console.log(signature)
- return signature
- } catch (error: unknown) {
- console.log('error', `Transaction failed! ${error}`, signature)
-
- return
- }
+ getTokenBalance: {
+ queryKey: ['getTokenBalance', { endpoint: connection?.rpcEndpoint, account: address }],
+ queryFn: () => connection.getTokenAccountBalance(address),
+ },
+ requestAirdrop: {
+ mutationKey: ['requestAirdrop', { endpoint: connection?.rpcEndpoint, address }],
+ mutationFn: (amount: string) => requestAndConfirmAirdrop({ address, amount, connection }),
+ },
+ transferSol: {
+ mutationKey: ['transferSol', { endpoint: connection?.rpcEndpoint, address }],
+ mutationFn: async ({ amount, destination }: { amount: string; destination: PublicKey }) => {
+ try {
+ const { transaction, latestBlockhash } = await createTransaction({
+ amount,
+ connection,
+ destination,
+ publicKey: address,
+ })
+
+ // Send transaction and await for signature
+ const signature: TransactionSignature = await wallet.sendTransaction(transaction, connection)
+
+ // Send transaction and await for signature
+ await connection.confirmTransaction({ signature, ...latestBlockhash }, 'confirmed')
+
+ return signature
+ } catch (error: unknown) {
+ console.log('error', `Transaction failed! ${error}`)
+ return
+ }
+ },
},
- onSuccess: (signature) => {
- if (signature) {
- transactionToast(signature)
- }
- return Promise.all([getBalance.refetch(), getSignatures.refetch()])
+ }
+}
+
+export function useGetBalance({ address }: { address: PublicKey }) {
+ return useQuery(useQueries({ address }).getBalance)
+}
+export function useGetSignatures({ address }: { address: PublicKey }) {
+ return useQuery(useQueries({ address }).getSignatures)
+}
+export function useGetTokenAccounts({ address }: { address: PublicKey }) {
+ return useQuery(useQueries({ address }).getTokenAccounts)
+}
+export function useGetTokenBalance({ address }: { address: PublicKey }) {
+ return useQuery(useQueries({ address }).getTokenBalance)
+}
+export function useRequestAirdrop({ address }: { address: PublicKey }) {
+ const {
+ requestAirdrop: { mutationKey, mutationFn },
+ } = useQueries({ address })
+ const onSuccess = useOnTransactionSuccess({ address })
+ return useMutation({
+ mutationKey,
+ mutationFn,
+ onSuccess,
+ onError: (error: unknown) => {
+ toastError(`Requesting airdrop failed! ${error}`)
},
- onError: (error) => {
- toastError(`Transaction failed! ${error}`)
+ })
+}
+export function useTransferSol({ address }: { address: PublicKey }) {
+ const onSuccess = useOnTransactionSuccess({ address })
+ return useMutation({
+ ...useQueries({ address }).transferSol,
+ onSuccess,
+ onError: (error: unknown) => {
+ toastError(`Sending transaction failed! ${error}`)
},
})
+}
- return {
- getBalance,
- getSignatures,
- getTokenAccounts,
- getTokenBalance,
- requestAirdrop,
- transferSol,
+async function getAllTokenAccounts(connection: Connection, address: PublicKey) {
+ const [tokenAccounts, token2022Accounts] = await Promise.all([
+ connection.getParsedTokenAccountsByOwner(address, { programId: TOKEN_PROGRAM_ID }),
+ connection.getParsedTokenAccountsByOwner(address, { programId: TOKEN_2022_PROGRAM_ID }),
+ ])
+ return [...tokenAccounts.value, ...token2022Accounts.value]
+}
+
+async function requestAndConfirmAirdrop({
+ address,
+ amount,
+ connection,
+}: {
+ connection: Connection
+ address: PublicKey
+ amount: string
+}) {
+ const [latestBlockhash, signature] = await Promise.all([
+ connection.getLatestBlockhash(),
+ connection.requestAirdrop(address, parseFloat(amount) * LAMPORTS_PER_SOL),
+ ])
+
+ await connection.confirmTransaction({ signature, ...latestBlockhash }, 'confirmed')
+ return signature
+}
+
+function useOnTransactionSuccess({ address }: { address: PublicKey }) {
+ const { getExplorerUrl } = useCluster()
+ const client = useQueryClient()
+ const { getBalance, getSignatures } = useQueries({ address })
+
+ return (signature?: TransactionSignature) => {
+ if (signature) {
+ uiToastLink({ link: getExplorerUrl(`tx/${signature}`), label: 'View Transaction' })
+ }
+ return Promise.all([
+ client.invalidateQueries({ queryKey: getBalance.queryKey }),
+ client.invalidateQueries({ queryKey: getSignatures.queryKey }),
+ ])
}
}
-async function createTransaction({
+export function uiToastLink({
+ label,
+ link,
+ ...props
+}: Omit & { link: string; label: string }) {
+ return toastSuccess({
+ ...props,
+ message: (
+
+ {label}
+
+ ),
+ })
+}
+
+export async function createTransaction({
publicKey,
destination,
amount,
@@ -137,7 +170,7 @@ async function createTransaction({
}: {
publicKey: PublicKey
destination: PublicKey
- amount: number
+ amount: string
connection: Connection
}): Promise<{
transaction: VersionedTransaction
@@ -151,7 +184,7 @@ async function createTransaction({
SystemProgram.transfer({
fromPubkey: publicKey,
toPubkey: destination,
- lamports: amount * LAMPORTS_PER_SOL,
+ lamports: parseFloat(amount) * LAMPORTS_PER_SOL,
}),
]
diff --git a/apps/web/src/app/features/account/account-ui.tsx b/apps/web/src/app/features/account/account-ui.tsx
index b902cd0..1849e07 100644
--- a/apps/web/src/app/features/account/account-ui.tsx
+++ b/apps/web/src/app/features/account/account-ui.tsx
@@ -22,7 +22,14 @@ import { useQueryClient } from '@tanstack/react-query'
import { useMemo, useState } from 'react'
import { useCluster } from '../cluster/cluster-data-access'
import { ExplorerLink } from '../cluster/cluster-ui'
-import { useAccount } from './account-data-access'
+import {
+ useGetBalance,
+ useGetSignatures,
+ useGetTokenAccounts,
+ useGetTokenBalance,
+ useRequestAirdrop,
+ useTransferSol,
+} from './account-data-access'
export function ellipsify(str = '', len = 4) {
if (str.length > 30) {
@@ -32,7 +39,7 @@ export function ellipsify(str = '', len = 4) {
}
export function AccountBalance({ address, ...props }: { address: PublicKey } & TitleProps) {
- const { getBalance: query } = useAccount({ address })
+ const query = useGetBalance({ address })
return (
query.refetch()} {...props}>
@@ -49,7 +56,8 @@ export function AccountChecker() {
}
export function AccountBalanceCheck({ address }: { address: PublicKey }) {
const { cluster } = useCluster()
- const { getBalance: query, requestAirdrop } = useAccount({ address })
+ const query = useGetBalance({ address })
+ const requestAirdrop = useRequestAirdrop({ address })
if (query.isLoading) {
return null
@@ -72,7 +80,7 @@ export function AccountBalanceCheck({ address }: { address: PublicKey }) {
variant="light"
color="yellow"
size="xs"
- onClick={() => requestAirdrop.mutateAsync(1).catch((err) => console.log(err))}
+ onClick={() => requestAirdrop.mutateAsync('1').catch((err) => console.log(err))}
>
Request Airdrop
@@ -99,7 +107,7 @@ export function AccountButtons({ address }: { address: PublicKey }) {
export function AccountTokens({ address }: { address: PublicKey }) {
const [showAll, setShowAll] = useState(false)
- const { getTokenAccounts: query } = useAccount({ address })
+ const query = useGetTokenAccounts({ address })
const client = useQueryClient()
const items = useMemo(() => {
if (showAll) return query.data
@@ -188,18 +196,18 @@ export function AccountTokens({ address }: { address: PublicKey }) {
}
export function AccountTokenBalance({ address, ...props }: { address: PublicKey } & TextProps) {
- const { getTokenBalance } = useAccount({ address })
- return getTokenBalance.isLoading ? (
+ const query = useGetTokenBalance({ address })
+ return query.isLoading ? (
- ) : getTokenBalance.data ? (
- {getTokenBalance.data?.value.uiAmount}
+ ) : query.data ? (
+ {query.data?.value.uiAmount}
) : (
Error
)
}
export function AccountTransactions({ address }: { address: PublicKey }) {
- const { getSignatures: query } = useAccount({ address })
+ const query = useGetSignatures({ address })
const [showAll, setShowAll] = useState(false)
const items = useMemo(() => {
@@ -294,8 +302,8 @@ function ModalReceive({ address, ...props }: { address: PublicKey }) {
function ModalAirdrop({ address, ...props }: ButtonProps & { address: PublicKey }) {
const [opened, { close, open }] = useDisclosure(false)
- const { requestAirdrop: mutation } = useAccount({ address })
- const [amount, setAmount] = useState(2)
+ const mutation = useRequestAirdrop({ address })
+ const [amount, setAmount] = useState('2')
return (
<>
@@ -306,9 +314,11 @@ function ModalAirdrop({ address, ...props }: ButtonProps & { address: PublicKey
setAmount(Number(e.target.value))}
+ onChange={(e) => setAmount(e.target.value)}
/>