Skip to content

Instantly share code, notes, and snippets.

@AlmostEfficient
Created January 20, 2025 23:24
Show Gist options
  • Save AlmostEfficient/2aba5ac4315fa3f2703cffb63b1f2082 to your computer and use it in GitHub Desktop.
Save AlmostEfficient/2aba5ac4315fa3f2703cffb63b1f2082 to your computer and use it in GitHub Desktop.
various hooks for solana actions
import {TOKEN_2022_PROGRAM_ID, TOKEN_PROGRAM_ID} from '@solana/spl-token'
import {useConnection, useWallet, WalletContextState} from '@solana/wallet-adapter-react'
import {
Connection,
LAMPORTS_PER_SOL,
PublicKey,
SystemProgram,
TransactionMessage,
TransactionSignature,
VersionedTransaction,
Commitment,
} from '@solana/web3.js'
import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'
export function useGetBalance({ address }: { address: PublicKey }) {
const { connection } = useConnection()
return useQuery({
queryKey: ['get-balance', { endpoint: connection.rpcEndpoint, address }],
queryFn: () => connection.getBalance(address),
})
}
export function useGetSignatures({ address }: { address: PublicKey }) {
const { connection } = useConnection()
return useQuery({
queryKey: ['get-signatures', { endpoint: connection.rpcEndpoint, address }],
queryFn: () => connection.getSignaturesForAddress(address),
})
}
export function useGetTokenAccounts({ address }: { address: PublicKey }) {
const { connection } = useConnection()
return useQuery({
queryKey: ['get-token-accounts', { endpoint: connection.rpcEndpoint, address }],
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]
},
})
}
export function useTransferSol({ address }: { address: PublicKey }) {
const { connection } = useConnection()
const wallet = useWallet()
const client = useQueryClient()
return useMutation({
mutationKey: ['transfer-sol', { endpoint: connection.rpcEndpoint, 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
}
},
onSuccess: (signature) => {
if (signature) {
console.log(`Transaction successful: https://solana.fm/tx/${signature}?cluster=devnet-solana`)
}
return Promise.all([
client.invalidateQueries({
queryKey: ['get-balance', { endpoint: connection.rpcEndpoint, address }],
}),
client.invalidateQueries({
queryKey: ['get-signatures', { endpoint: connection.rpcEndpoint, address }],
}),
])
},
onError: (error) => {
console.error(`Transaction failed! ${error}`)
},
})
}
async function createTransaction({
publicKey,
destination,
amount,
connection,
}: {
publicKey: PublicKey
destination: PublicKey
amount: number
connection: Connection
}): Promise<{
transaction: VersionedTransaction
latestBlockhash: { blockhash: string; lastValidBlockHeight: number }
}> {
// Get the latest blockhash to use in our transaction
const latestBlockhash = await connection.getLatestBlockhash()
// Create instructions to send, in this case a simple transfer
const instructions = [
SystemProgram.transfer({
fromPubkey: publicKey,
toPubkey: destination,
lamports: amount * LAMPORTS_PER_SOL,
}),
]
// Create a new TransactionMessage with version and compile it to legacy
const messageLegacy = new TransactionMessage({
payerKey: publicKey,
recentBlockhash: latestBlockhash.blockhash,
instructions,
}).compileToLegacyMessage()
// Create a new VersionedTransaction which supports legacy and v0
const transaction = new VersionedTransaction(messageLegacy)
return {
transaction,
latestBlockhash,
}
}
export async function sendAndConfirmVersionedTransaction({
connection,
transaction,
wallet: { signTransaction, publicKey },
commitment = 'confirmed',
onError = (e: Error) => console.error(`error sending tx: ${e}`),
onSuccess = (txid: string) => {
const cluster = connection.rpcEndpoint.includes('devnet') ? 'devnet' :
connection.rpcEndpoint.includes('testnet') ? 'testnet' : 'mainnet';
console.log(`Transaction successful: https://solana.fm/tx/${txid}?cluster=${cluster}-solana`);
},
}: {
connection: Connection
transaction: VersionedTransaction
wallet: Pick<WalletContextState, 'signTransaction' | 'publicKey'>
commitment?: Commitment
onError?: (e: Error) => void
onSuccess?: (txid: string) => void
}) {
if (!signTransaction) throw new Error('Wallet does not support signing')
const signedTx = await signTransaction(transaction)
const serializedTx = signedTx.serialize()
const latestBlockhash = await connection.getLatestBlockhash()
const txid = await connection.sendRawTransaction(serializedTx, {
skipPreflight: false,
})
const controller = new AbortController()
// Background rebroadcasting
const abortableResender = async () => {
while (true) {
await wait(2000)
if (controller.signal.aborted) return
try {
await connection.sendRawTransaction(serializedTx, { skipPreflight: true })
} catch (e) {
console.warn(`Failed to resend: ${e}`)
}
}
}
try {
abortableResender()
await Promise.race([
connection.confirmTransaction({
signature: txid,
...latestBlockhash,
abortSignal: controller.signal,
}, commitment),
new Promise(async (resolve) => {
while (!controller.signal.aborted) {
await wait(2000)
const status = await connection.getSignatureStatus(txid)
if (status?.value?.confirmationStatus === commitment) {
resolve(status)
}
}
}),
])
onSuccess(txid)
return txid
} catch (e) {
onError(e as Error)
throw e
} finally {
controller.abort()
}
}
export const wait = (time: number) =>
new Promise((resolve) => setTimeout(resolve, time));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment