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 2991a03..e5649b1 100644 --- a/components/CreateTransactionButton.tsx +++ b/components/CreateTransactionButton.tsx @@ -14,9 +14,9 @@ import * as multisig from "@sqds/multisig"; import { useWallet } from "@solana/wallet-adapter-react"; import { Connection, + Message, PublicKey, TransactionInstruction, - TransactionMessage, clusterApiUrl, } from "@solana/web3.js"; import { Input } from "./ui/input"; @@ -54,7 +54,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 +72,7 @@ const CreateTransaction = ({ ], payerKey: vaultAddress, recentBlockhash: (await connection.getLatestBlockhash()).blockhash, - }).compileToLegacyMessage(); + }); const encoded = bs58.default.encode(dummyMessage.serialize()); @@ -100,17 +100,22 @@ const CreateTransaction = ({ />
diff --git a/lib/transaction/decodeAndDeserialize.ts b/lib/transaction/decodeAndDeserialize.ts index 31cd53d..c31b13b 100644 --- a/lib/transaction/decodeAndDeserialize.ts +++ b/lib/transaction/decodeAndDeserialize.ts @@ -1,18 +1,66 @@ import * as bs58 from "bs58"; -import { 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); - const message = VersionedMessage.deserialize(messageBytes); + let message: TransactionMessage; + let accountKeys: PublicKey[]; + + if (version === "legacy") { + const legacyMessage = Message.from(messageBytes); + accountKeys = legacyMessage.accountKeys; + + const intermediate = VersionedMessage.deserialize( + new MessageV0(legacyMessage).serialize() + ); + message = TransactionMessage.decompile(intermediate, { + addressLookupTableAccounts: [], + }); + } else { + 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/getAccountsForSimulation.ts b/lib/transaction/getAccountsForSimulation.ts index 4d7270b..767d1af 100644 --- a/lib/transaction/getAccountsForSimulation.ts +++ b/lib/transaction/getAccountsForSimulation.ts @@ -26,7 +26,12 @@ export async function getAccountsForSimulation( const { staticAccountKeys, accountKeysFromLookups } = tx.message.getAccountKeys({ addressLookupTableAccounts }); - const staticAddresses = staticAccountKeys.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()) 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 diff --git a/lib/transaction/simulateEncodedTransaction.ts b/lib/transaction/simulateEncodedTransaction.ts index 195fdb6..267ec3d 100644 --- a/lib/transaction/simulateEncodedTransaction.ts +++ b/lib/transaction/simulateEncodedTransaction.ts @@ -15,12 +15,12 @@ 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, transaction, - version === "legacy" + version === 0 ); toast.loading("Simulating...", {