Skip to content

Commit

Permalink
feat: clean up Solana hooks
Browse files Browse the repository at this point in the history
  • Loading branch information
beeman committed Jan 6, 2024
1 parent 330628a commit 3d42090
Show file tree
Hide file tree
Showing 19 changed files with 196 additions and 157 deletions.
4 changes: 2 additions & 2 deletions libs/web/solana/data-access/src/lib/create-transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export async function createTransaction({
}: {
publicKey: PublicKey
destination: PublicKey
amount: number
amount: string
connection: Connection
}): Promise<{
transaction: VersionedTransaction
Expand All @@ -29,7 +29,7 @@ export async function createTransaction({
SystemProgram.transfer({
fromPubkey: publicKey,
toPubkey: destination,
lamports: amount * LAMPORTS_PER_SOL,
lamports: parseFloat(amount) * LAMPORTS_PER_SOL,
}),
]

Expand Down
199 changes: 115 additions & 84 deletions libs/web/solana/data-access/src/lib/use-account.tsx
Original file line number Diff line number Diff line change
@@ -1,108 +1,139 @@
import { toastError } from '@pubkey-ui/core'
import { TOKEN_2022_PROGRAM_ID, TOKEN_PROGRAM_ID } from '@solana/spl-token'
import { useConnection, useWallet } from '@solana/wallet-adapter-react'
import { LAMPORTS_PER_SOL, PublicKey, TransactionSignature } from '@solana/web3.js'
import { useMutation, useQuery } from '@tanstack/react-query'
import { Connection, LAMPORTS_PER_SOL, PublicKey, TransactionSignature } from '@solana/web3.js'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { createTransaction } from './create-transaction'
import { toastExplorerLink } from './toast-signature-link'
import { useCluster } from './web-cluster-provider'

export function useAccount({ address }: { address: PublicKey }) {
const { cluster, getExplorerUrl } = 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),
},
})
getSignatures: {
queryKey: ['getSignatures', { endpoint: connection?.rpcEndpoint, address }],
queryFn: () => connection.getConfirmedSignaturesForAddress2(address),
},
getTokenAccounts: {
queryKey: ['getTokenAccounts', { endpoint: connection?.rpcEndpoint, address }],
queryFn: () => getAllTokenAccounts(connection, address),
},
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,
})

const getTokenBalance = useQuery({
queryKey: ['getTokenAccountBalance', { endpoint: connection.rpcEndpoint, account: address.toString() }],
queryFn: () => connection.getTokenAccountBalance(address),
})
// Send transaction and await for signature
const signature: TransactionSignature = await wallet.sendTransaction(transaction, connection)

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),
])
// Send transaction and await for signature
await connection.confirmTransaction({ signature, ...latestBlockhash }, 'confirmed')

await connection.confirmTransaction({ signature, ...latestBlockhash }, 'confirmed')
return signature
return signature
} catch (error: unknown) {
console.log('error', `Transaction failed! ${error}`)
return
}
},
},
onSuccess: (signature) => {
toastExplorerLink({ link: getExplorerUrl(`tx/${signature}`), label: 'View Transaction' })
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}`)
},
})
}
export function useTransferSol({ address }: { address: PublicKey }) {
const onSuccess = useOnTransactionSuccess({ address })
return useMutation({
...useQueries({ address }).transferSol,
onSuccess,
onError: (error: unknown) => {
toastError(`Requesting airdrop failed! ${error}`)
},
})
}

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)
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]
}

// Send transaction and await for signature
await connection.confirmTransaction({ signature, ...latestBlockhash }, 'confirmed')
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),
])

console.log(signature)
return signature
} catch (error: unknown) {
console.log('error', `Transaction failed! ${error}`, signature)
await connection.confirmTransaction({ signature, ...latestBlockhash }, 'confirmed')
return signature
}

return
}
},
onSuccess: (signature) => {
if (signature) {
toastExplorerLink({ link: getExplorerUrl(`tx/${signature}`), label: 'View Transaction' })
}
return Promise.all([getBalance.refetch(), getSignatures.refetch()])
},
onError: (error) => {
toastError(`Transaction failed! ${error}`)
},
})
function useOnTransactionSuccess({ address }: { address: PublicKey }) {
const { getExplorerUrl } = useCluster()
const client = useQueryClient()
const { getBalance, getSignatures } = useQueries({ address })

return {
getBalance,
getSignatures,
getTokenAccounts,
getTokenBalance,
requestAirdrop,
transferSol,
return (signature?: TransactionSignature) => {
if (signature) {
toastExplorerLink({ link: getExplorerUrl(`tx/${signature}`), label: 'View Transaction' })
}
return Promise.all([
client.invalidateQueries({ queryKey: getBalance.queryKey }),
client.invalidateQueries({ queryKey: getSignatures.queryKey }),
])
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,15 @@ import { Program, ProgramAccount } from '@coral-xyz/anchor'
import { Counter } from '@pubkey-stack/anchor'
import { createContext, ReactNode, useContext } from 'react'
import { useCounterProgram } from './use-counter-program'
import { useCluster } from '@pubkey-stack/web-solana-data-access'
import { useCounterFetch } from './use-counter-fetch'
import { useCounterRefresh } from './use-counter-refresh'

export interface CounterProgramAccountProviderContext {
account: ProgramAccount<{ count: number }>
program: Program<Counter>
refresh: () => Promise<void>
getExplorerUrl: (path: string) => string
}

const Context = createContext<CounterProgramAccountProviderContext>({} as CounterProgramAccountProviderContext)
Expand All @@ -18,12 +23,19 @@ export function CounterProgramAccountProvider({
children: ReactNode
}) {
const program = useCounterProgram()
const { getExplorerUrl } = useCluster()
const counterRefresh = useCounterRefresh()
const counterQuery = useCounterFetch({ account: account.publicKey })

return (
<Context.Provider
value={{
account,
program,
refresh: async () => {
await Promise.all([counterQuery.refetch(), counterRefresh()])
},
getExplorerUrl,
}}
>
{children}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,10 @@
import { toastExplorerLink, useAccount, useCluster } from '@pubkey-stack/web-solana-data-access'
import { toastExplorerLink } from '@pubkey-stack/web-solana-data-access'
import { toastError } from '@pubkey-ui/core'
import { useWallet } from '@solana/wallet-adapter-react'
import { useMutation } from '@tanstack/react-query'
import { useCounterProgramAccount } from './counter-program-account-provider'
import { useCounterFetchAll } from './use-counter-fetch-all'

export function useCounterClose() {
const fetchAll = useCounterFetchAll()
const { account, program } = useCounterProgramAccount()
const { publicKey } = useWallet()
const { getBalance } = useAccount({ address: publicKey! })
const { getExplorerUrl } = useCluster()
const { account, program, refresh, getExplorerUrl } = useCounterProgramAccount()

return useMutation({
mutationKey: ['counter', 'close', { account }],
Expand All @@ -26,7 +20,7 @@ export function useCounterClose() {
label: 'View transaction',
})
}
return Promise.all([fetchAll.refetch(), getBalance.refetch()])
return refresh()
})
.catch((err) => toastError(err.message)),
})
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
import { toastExplorerLink, useAccount, useCluster } from '@pubkey-stack/web-solana-data-access'
import { toastExplorerLink } from '@pubkey-stack/web-solana-data-access'
import { toastError } from '@pubkey-ui/core'
import { useMutation } from '@tanstack/react-query'
import { useCounterProgramAccount } from './counter-program-account-provider'
import { useCounterFetch } from './use-counter-fetch'

export function useCounterDecrement() {
const counterQuery = useCounterFetch()
const { account, program } = useCounterProgramAccount()
const { getBalance } = useAccount({ address: account?.publicKey })
const { getExplorerUrl } = useCluster()
const { account, program, getExplorerUrl, refresh } = useCounterProgramAccount()

return useMutation({
mutationKey: ['counter', 'decrement', { account }],
Expand All @@ -22,7 +18,7 @@ export function useCounterDecrement() {
link: getExplorerUrl(`/tx/${signature}`),
label: 'View transaction',
})
return Promise.all([counterQuery.refetch(), getBalance.refetch()])
return refresh()
})
.catch((err) => toastError(err.message)),
})
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { toastError } from '@pubkey-ui/core'
import { useQuery } from '@tanstack/react-query'
import { useCounterProgramAccount } from './counter-program-account-provider'
import { useCounterProgram } from './use-counter-program'
import { PublicKey } from '@solana/web3.js'

export function useCounterFetch() {
const { account } = useCounterProgramAccount()
export function useCounterFetch({ account }: { account: PublicKey }) {
const program = useCounterProgram()

return useQuery({
queryKey: ['counter', 'fetch', { account }],
queryFn: () => program.account.counter.fetch(account.publicKey).catch((err) => toastError(err.message)),
queryFn: () => program.account.counter.fetch(account).catch((err) => toastError(err.message)),
})
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,10 @@
import { toastExplorerLink, useAccount, useCluster } from '@pubkey-stack/web-solana-data-access'
import { toastExplorerLink } from '@pubkey-stack/web-solana-data-access'
import { toastError } from '@pubkey-ui/core'
import { useWallet } from '@solana/wallet-adapter-react'
import { useMutation } from '@tanstack/react-query'
import { useCounterProgramAccount } from './counter-program-account-provider'
import { useCounterFetch } from './use-counter-fetch'

export function useCounterIncrement() {
const { publicKey } = useWallet()
const { getBalance } = useAccount({ address: publicKey! })
const counterQuery = useCounterFetch()
const { account, program } = useCounterProgramAccount()
const { getExplorerUrl } = useCluster()
const { account, program, refresh, getExplorerUrl } = useCounterProgramAccount()

return useMutation({
mutationKey: ['counter', 'increment', { account }],
Expand All @@ -24,7 +18,7 @@ export function useCounterIncrement() {
link: getExplorerUrl(`/tx/${signature}`),
label: 'View transaction',
})
return Promise.all([counterQuery.refetch(), getBalance.refetch()])
return refresh()
})
.catch((err) => toastError(err.message)),
})
Expand Down
Loading

0 comments on commit 3d42090

Please sign in to comment.