From cd612ec1b18deaa9fb2c995bd9546c04de7dec1d Mon Sep 17 00:00:00 2001 From: "amilz.sol" <85324096+amilz@users.noreply.github.com> Date: Mon, 1 Jul 2024 15:58:52 -0700 Subject: [PATCH] Update useTransactionProcessor.ts implement legacy/versioned tx handling --- .../hooks/useTransactionProcessor.ts | 72 +++++++++++-------- 1 file changed, 43 insertions(+), 29 deletions(-) diff --git a/sample-dapps/solana-action-blinker/hooks/useTransactionProcessor.ts b/sample-dapps/solana-action-blinker/hooks/useTransactionProcessor.ts index ad4fae9..3bc37f9 100644 --- a/sample-dapps/solana-action-blinker/hooks/useTransactionProcessor.ts +++ b/sample-dapps/solana-action-blinker/hooks/useTransactionProcessor.ts @@ -1,5 +1,5 @@ -import { useState, useEffect } from 'react'; -import { Transaction, VersionedMessage, VersionedTransaction } from "@solana/web3.js"; +import { useState, useEffect, useCallback } from 'react'; +import { Transaction, VersionedTransaction } from "@solana/web3.js"; import { useWallet } from "@solana/wallet-adapter-react"; import { getExplorerUrl } from "@/utils/solana"; import { ActionPostResponse } from '@solana/actions'; @@ -11,7 +11,7 @@ const useTransactionProcessor = (postResults?: ActionPostResponse | null) => { const [transactionUrl, setTransactionUrl] = useState(null); const [isLoading, setIsLoading] = useState(false); - const sendAndConfirm = async (serializedTx: string) => { + const sendAndConfirm = useCallback(async (serializedTx: string): Promise => { if (!serializedTx) throw new Error('No transaction to send'); const API_ENDPOINT = '/api/solana/sendAndConfirm'; @@ -20,45 +20,59 @@ const useTransactionProcessor = (postResults?: ActionPostResponse | null) => { try { const response = await fetch(API_ENDPOINT, { method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, + headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ serializedTx }), }); - if (response.ok) { - const data = await response.json(); - return data.signature; - } else { - setTransactionError('Failed to send and confirm Tx'); + if (!response.ok) { throw new Error('Failed to send and confirm Tx'); } + const data = await response.json(); + return data.signature; } catch (error) { setTransactionError('Failed to send and confirm Tx'); - throw new Error('Failed to send and confirm Tx'); + throw error; } finally { setIsLoading(false); } - } + }, []); + + const processLegacyTransaction = useCallback(async (transaction: string): Promise => { + if (!signTransaction) throw new Error('Wallet not connected'); + + const txPartial = Transaction.from(Buffer.from(transaction, 'base64')); + const txSigned = await signTransaction(txPartial); + const txFullSerialized = txSigned.serialize({ requireAllSignatures: true }); + const txFullBase64 = txFullSerialized.toString('base64'); + return await sendAndConfirm(txFullBase64); + }, [signTransaction, sendAndConfirm]); + + const processVersionedTransaction = useCallback(async (transaction: string): Promise => { + if (!signTransaction) throw new Error('Wallet not connected'); + + const base64Encoder = getBase64Encoder(); + const transactionUint8Array = new Uint8Array(base64Encoder.encode(transaction)); + const transactionV2 = VersionedTransaction.deserialize(transactionUint8Array); + const txSigned = await signTransaction(transactionV2); + const txFullSerialized = txSigned.serialize(); + const txFullBase64 = Buffer.from(txFullSerialized).toString('base64'); + return await sendAndConfirm(txFullBase64); + }, [signTransaction, sendAndConfirm]); + useEffect(() => { const processTransaction = async () => { - if (!postResults || !signTransaction) return; + if (!postResults?.transaction || !signTransaction) return; try { - const { transaction } = postResults; - if (!transaction) { - throw new Error("Failed to generate transaction"); + let signature: string; + try { + signature = await processLegacyTransaction(postResults.transaction); + } catch (err) { + if (err instanceof Error && err.message.includes('VersionedMessage.deserialize()')) { + signature = await processVersionedTransaction(postResults.transaction); + } else { + throw err; + } } - - // Thanks @Callum - // https://solana.stackexchange.com/questions/9775/how-to-deserialize-a-magic-links-versioned-transaction - const base64Encoder = getBase64Encoder(); - const readonlyTransactionUint8Array = base64Encoder.encode(transaction); - const transactionUint8Array = new Uint8Array(readonlyTransactionUint8Array); - const transactionV2 = VersionedTransaction.deserialize(transactionUint8Array); - const txSigned = await signTransaction(transactionV2); - const txFullSerialized = txSigned.serialize(); - const txFullBase64 = txFullSerialized.toString(); - const signature = await sendAndConfirm(txFullBase64); const explorerUrl = getExplorerUrl(signature); setTransactionUrl(explorerUrl); } catch (err) { @@ -68,7 +82,7 @@ const useTransactionProcessor = (postResults?: ActionPostResponse | null) => { }; processTransaction(); - }, [postResults, signTransaction]); + }, [postResults, signTransaction, processLegacyTransaction, processVersionedTransaction]); return { transactionError, transactionUrl, isLoading }; };