From f0db85486d47ad0458af59fd0d017022a2e2ae9a Mon Sep 17 00:00:00 2001 From: Joey Meere <100378695+joeymeere@users.noreply.github.com> Date: Tue, 5 Nov 2024 13:18:31 -0500 Subject: [PATCH 1/5] fix(tx): handle generic messages as input --- components/CreateTransactionButton.tsx | 5 +++-- lib/transaction/decodeAndDeserialize.ts | 12 ++++++++++-- lib/transaction/getAccountsForSimulation.ts | 4 +++- lib/transaction/simulateEncodedTransaction.ts | 2 +- 4 files changed, 17 insertions(+), 6 deletions(-) diff --git a/components/CreateTransactionButton.tsx b/components/CreateTransactionButton.tsx index 2991a03..b31f147 100644 --- a/components/CreateTransactionButton.tsx +++ b/components/CreateTransactionButton.tsx @@ -14,6 +14,7 @@ import * as multisig from "@sqds/multisig"; import { useWallet } from "@solana/wallet-adapter-react"; import { Connection, + Message, PublicKey, TransactionInstruction, TransactionMessage, @@ -54,7 +55,7 @@ const CreateTransaction = ({ programId: programId ? new PublicKey(programId) : multisig.PROGRAM_ID, })[0]; - const dummyMessage = new TransactionMessage({ + const dummyMessage = Message.compile({ instructions: [ new TransactionInstruction({ keys: [ @@ -72,7 +73,7 @@ const CreateTransaction = ({ ], payerKey: vaultAddress, recentBlockhash: (await connection.getLatestBlockhash()).blockhash, - }).compileToLegacyMessage(); + }); const encoded = bs58.default.encode(dummyMessage.serialize()); diff --git a/lib/transaction/decodeAndDeserialize.ts b/lib/transaction/decodeAndDeserialize.ts index 31cd53d..89f3e5a 100644 --- a/lib/transaction/decodeAndDeserialize.ts +++ b/lib/transaction/decodeAndDeserialize.ts @@ -1,5 +1,5 @@ import * as bs58 from "bs58"; -import { VersionedMessage } from "@solana/web3.js"; +import { Message, TransactionMessage, VersionedMessage } from "@solana/web3.js"; export function decodeAndDeserialize(tx: string): { message: VersionedMessage; @@ -8,7 +8,15 @@ export function decodeAndDeserialize(tx: string): { try { const messageBytes = bs58.default.decode(tx); const version = VersionedMessage.deserializeMessageVersion(messageBytes); - const message = VersionedMessage.deserialize(messageBytes); + + let message; + if (version === "legacy") { + let legMsg = Message.from(messageBytes); + let converted = TransactionMessage.decompile(legMsg).compileToV0Message(); + message = VersionedMessage.deserialize(converted.serialize()); + } else { + message = VersionedMessage.deserialize(messageBytes); + } return { version, message }; } catch (error) { diff --git a/lib/transaction/getAccountsForSimulation.ts b/lib/transaction/getAccountsForSimulation.ts index 4d7270b..a493ba6 100644 --- a/lib/transaction/getAccountsForSimulation.ts +++ b/lib/transaction/getAccountsForSimulation.ts @@ -26,7 +26,9 @@ export async function getAccountsForSimulation( const { staticAccountKeys, accountKeysFromLookups } = tx.message.getAccountKeys({ addressLookupTableAccounts }); - const staticAddresses = staticAccountKeys.map((k) => k.toString()); + const staticAddresses = staticAccountKeys + .filter((k) => !k.equals(SystemProgram.programId)) + .map((k) => k.toString()); const addressesFromLookups = accountKeysFromLookups ? accountKeysFromLookups.writable.map((k) => k.toString()) diff --git a/lib/transaction/simulateEncodedTransaction.ts b/lib/transaction/simulateEncodedTransaction.ts index 195fdb6..503d230 100644 --- a/lib/transaction/simulateEncodedTransaction.ts +++ b/lib/transaction/simulateEncodedTransaction.ts @@ -20,7 +20,7 @@ export const simulateEncodedTransaction = async ( const keys = await getAccountsForSimulation( connection, transaction, - version === "legacy" + version === 0 ); toast.loading("Simulating...", { From ac98aff7738046dd1af1697fb8e374d862e67eab Mon Sep 17 00:00:00 2001 From: Joey Meere <100378695+joeymeere@users.noreply.github.com> Date: Wed, 6 Nov 2024 09:35:38 -0500 Subject: [PATCH 2/5] remove decompile --- lib/transaction/decodeAndDeserialize.ts | 64 ++++++++++++++++++++----- lib/transaction/importTransaction.ts | 3 +- 2 files changed, 54 insertions(+), 13 deletions(-) diff --git a/lib/transaction/decodeAndDeserialize.ts b/lib/transaction/decodeAndDeserialize.ts index 89f3e5a..c31b13b 100644 --- a/lib/transaction/decodeAndDeserialize.ts +++ b/lib/transaction/decodeAndDeserialize.ts @@ -1,26 +1,66 @@ import * as bs58 from "bs58"; -import { Message, TransactionMessage, VersionedMessage } from "@solana/web3.js"; +import { + Message, + MessageAccountKeys, + MessageV0, + PublicKey, + Transaction, + TransactionMessage, + VersionedMessage, + VersionedTransaction, +} from "@solana/web3.js"; -export function decodeAndDeserialize(tx: string): { - message: VersionedMessage; +interface DeserializedTransaction { + message: TransactionMessage; version: number | "legacy"; -} { + accountKeys: PublicKey[]; +} + +/** + * Decodes a base58 encoded transaction and deserializes it into a TransactionMessage + * @param tx - Base58 encoded transaction string + * @returns Object containing the deserialized message, version, and account keys + * @throws Error if deserialization fails + */ +export function decodeAndDeserialize(tx: string): DeserializedTransaction { + if (!tx) { + throw new Error("Transaction string is required"); + } + try { const messageBytes = bs58.default.decode(tx); const version = VersionedMessage.deserializeMessageVersion(messageBytes); + let message: TransactionMessage; + let accountKeys: PublicKey[]; - let message; if (version === "legacy") { - let legMsg = Message.from(messageBytes); - let converted = TransactionMessage.decompile(legMsg).compileToV0Message(); - message = VersionedMessage.deserialize(converted.serialize()); + const legacyMessage = Message.from(messageBytes); + accountKeys = legacyMessage.accountKeys; + + const intermediate = VersionedMessage.deserialize( + new MessageV0(legacyMessage).serialize() + ); + message = TransactionMessage.decompile(intermediate, { + addressLookupTableAccounts: [], + }); } else { - message = VersionedMessage.deserialize(messageBytes); + const versionedMessage = VersionedMessage.deserialize(messageBytes); + accountKeys = versionedMessage.staticAccountKeys; + + message = TransactionMessage.decompile(versionedMessage, { + addressLookupTableAccounts: [], + }); } - return { version, message }; + return { + version, + message, + accountKeys, + }; } catch (error) { - console.error(error); - throw new Error("Failed to decode transaction."); + if (error instanceof Error) { + throw new Error(`Failed to decode transaction: ${error.message}`); + } + throw new Error("Failed to decode transaction: Unknown error"); } } diff --git a/lib/transaction/importTransaction.ts b/lib/transaction/importTransaction.ts index 9eaa0ad..4a34356 100644 --- a/lib/transaction/importTransaction.ts +++ b/lib/transaction/importTransaction.ts @@ -3,6 +3,7 @@ import { Connection, PublicKey, TransactionMessage, + VersionedMessage, VersionedTransaction, } from "@solana/web3.js"; import { decodeAndDeserialize } from "./decodeAndDeserialize"; @@ -29,7 +30,7 @@ export const importTransaction = async ( new PublicKey(multisigPda) ); - const transactionMessage = TransactionMessage.decompile(message); + const transactionMessage = new TransactionMessage(message); const addressLookupTableAccounts = version === 0 From 1b24e7e930b2f277046b72cc324364f876bff672 Mon Sep 17 00:00:00 2001 From: Joey Meere <100378695+joeymeere@users.noreply.github.com> Date: Wed, 6 Nov 2024 09:40:28 -0500 Subject: [PATCH 3/5] handle message in simulation --- lib/transaction/simulateEncodedTransaction.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/transaction/simulateEncodedTransaction.ts b/lib/transaction/simulateEncodedTransaction.ts index 503d230..267ec3d 100644 --- a/lib/transaction/simulateEncodedTransaction.ts +++ b/lib/transaction/simulateEncodedTransaction.ts @@ -15,7 +15,7 @@ export const simulateEncodedTransaction = async ( try { const { message, version } = decodeAndDeserialize(tx); - const transaction = new VersionedTransaction(message); + const transaction = new VersionedTransaction(message.compileToV0Message()); const keys = await getAccountsForSimulation( connection, From 08144c3cac7de714d72986555586a706975e99b0 Mon Sep 17 00:00:00 2001 From: Joey Meere <100378695+joeymeere@users.noreply.github.com> Date: Thu, 7 Nov 2024 10:12:33 -0500 Subject: [PATCH 4/5] add alt-svm sim warning simulations may fail on alt-SVM environments due to some accounts not being found --- app/(app)/layout.tsx | 2 ++ components/CreateTransactionButton.tsx | 14 +++++++++----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/app/(app)/layout.tsx b/app/(app)/layout.tsx index 79d57bf..3a22355 100644 --- a/app/(app)/layout.tsx +++ b/app/(app)/layout.tsx @@ -121,6 +121,8 @@ const AppLayout = async ({ children }: { children: React.ReactNode }) => { , success: , diff --git a/components/CreateTransactionButton.tsx b/components/CreateTransactionButton.tsx index b31f147..e5649b1 100644 --- a/components/CreateTransactionButton.tsx +++ b/components/CreateTransactionButton.tsx @@ -17,7 +17,6 @@ import { Message, PublicKey, TransactionInstruction, - TransactionMessage, clusterApiUrl, } from "@solana/web3.js"; import { Input } from "./ui/input"; @@ -101,17 +100,22 @@ const CreateTransaction = ({ />
From 425f30ff2a544bf81acf4c53045d295712feae97 Mon Sep 17 00:00:00 2001 From: Joey Meere <100378695+joeymeere@users.noreply.github.com> Date: Thu, 7 Nov 2024 10:14:53 -0500 Subject: [PATCH 5/5] use reduce > find+map --- lib/transaction/getAccountsForSimulation.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/transaction/getAccountsForSimulation.ts b/lib/transaction/getAccountsForSimulation.ts index a493ba6..767d1af 100644 --- a/lib/transaction/getAccountsForSimulation.ts +++ b/lib/transaction/getAccountsForSimulation.ts @@ -26,9 +26,12 @@ export async function getAccountsForSimulation( const { staticAccountKeys, accountKeysFromLookups } = tx.message.getAccountKeys({ addressLookupTableAccounts }); - const staticAddresses = staticAccountKeys - .filter((k) => !k.equals(SystemProgram.programId)) - .map((k) => k.toString()); + const staticAddresses = staticAccountKeys.reduce((acc, k) => { + if (!k.equals(SystemProgram.programId)) { + acc.push(k.toString()); + } + return acc; + }, [] as string[]); const addressesFromLookups = accountKeysFromLookups ? accountKeysFromLookups.writable.map((k) => k.toString())