Skip to content

Commit

Permalink
feat: clean up useAccount hook
Browse files Browse the repository at this point in the history
  • Loading branch information
beeman committed Jan 9, 2024
1 parent 1a0ce72 commit b324edc
Show file tree
Hide file tree
Showing 2 changed files with 166 additions and 121 deletions.
243 changes: 138 additions & 105 deletions apps/web/src/app/features/account/account-data-access.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,134 +10,167 @@ 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: `
<div>
<div>
Transaction ${signature} sent
</div>
<a href="https://explorer.solana.com/tx/${signature}" target="_blank" rel="noopener noreferrer">
View on Solana Explorer
</a>
</div>`,
})
}

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<NotificationData, 'message'> & { link: string; label: string }) {
return toastSuccess({
...props,
message: (
<Anchor c="brand" href={link} target="_blank" rel="noopener noreferrer">
{label}
</Anchor>
),
})
}

export async function createTransaction({
publicKey,
destination,
amount,
connection,
}: {
publicKey: PublicKey
destination: PublicKey
amount: number
amount: string
connection: Connection
}): Promise<{
transaction: VersionedTransaction
Expand All @@ -151,7 +184,7 @@ async function createTransaction({
SystemProgram.transfer({
fromPubkey: publicKey,
toPubkey: destination,
lamports: amount * LAMPORTS_PER_SOL,
lamports: parseFloat(amount) * LAMPORTS_PER_SOL,
}),
]

Expand Down
Loading

0 comments on commit b324edc

Please sign in to comment.