From 8b252e00a39778fe7657493733c48e3a93ccbd70 Mon Sep 17 00:00:00 2001 From: Keyur Vaitha Date: Tue, 5 May 2026 18:59:50 +0530 Subject: [PATCH 1/2] feat: add v0 txn support for solana --- .../src/operations/signTxn/index.ts | 67 ++++++++++++++----- 1 file changed, 51 insertions(+), 16 deletions(-) diff --git a/packages/app-solana/src/operations/signTxn/index.ts b/packages/app-solana/src/operations/signTxn/index.ts index 7ccfb65d..6d946f4f 100644 --- a/packages/app-solana/src/operations/signTxn/index.ts +++ b/packages/app-solana/src/operations/signTxn/index.ts @@ -100,22 +100,57 @@ export const signTxn = async ( let serializedTxnHex: string | undefined; if (params.serializeTxn) { - const solanaWeb3 = getSolanaWeb3(); - const transaction = solanaWeb3.Transaction.populate( - solanaWeb3.Message.from(Buffer.from(params.txn, 'hex')), - ); - if (latestBlockHash) transaction.recentBlockhash = latestBlockHash; - assert( - transaction.feePayer, - new Error('Cannot decode feePayer in solana txn'), - ); - transaction.addSignature( - transaction.feePayer, - Buffer.from(signature, 'hex'), - ); - const serializedTxnBuffer = transaction.serialize(); - serializedTxnHex = uint8ArrayToHex(serializedTxnBuffer); - serializedTxn = base58Encode(serializedTxnBuffer); + const msgBytes = Buffer.from(params.txn, 'hex'); + const isVersioned = msgBytes.length > 0 && msgBytes[0] === 0x80; + + if (isVersioned) { + const updatedMsgBytes = Buffer.from(msgBytes); + if (latestBlockHash) { + const bhBytes = Buffer.from(base58Decode(latestBlockHash)); + /* Locate blockhash in v0 message: + [0x80 version] [3 header bytes] [compact-u16 acct count] [acct keys * 32] [32 blockhash] + See: https://solana.com/docs/core/transactions/versioned-transactions */ + let off = 4; + let acctCount = 0; + let shift = 0; + while (off < updatedMsgBytes.length) { + const b = updatedMsgBytes[off]; + off += 1; + acctCount += (b % 128) * 2 ** shift; + if (b < 128) break; + shift += 7; + } + off += acctCount * 32; + bhBytes.copy(updatedMsgBytes, off); + } + + const sigCount = Buffer.from([0x01]); + const sigBytes = Buffer.from(signature, 'hex'); + const serializedTxnBuffer = Buffer.concat([ + sigCount, + sigBytes, + updatedMsgBytes, + ]); + serializedTxnHex = uint8ArrayToHex(serializedTxnBuffer); + serializedTxn = base58Encode(serializedTxnBuffer); + } else { + const solanaWeb3 = getSolanaWeb3(); + const transaction = solanaWeb3.Transaction.populate( + solanaWeb3.Message.from(msgBytes), + ); + if (latestBlockHash) transaction.recentBlockhash = latestBlockHash; + assert( + transaction.feePayer, + new Error('Cannot decode feePayer in solana txn'), + ); + transaction.addSignature( + transaction.feePayer, + Buffer.from(signature, 'hex'), + ); + const serializedTxnBuffer = transaction.serialize(); + serializedTxnHex = uint8ArrayToHex(serializedTxnBuffer); + serializedTxn = base58Encode(serializedTxnBuffer); + } } return { From e8fdb82a0cbdcaf761349feb870ae10d31b041b5 Mon Sep 17 00:00:00 2001 From: Keyur Vaitha Date: Mon, 1 Jun 2026 19:33:54 +0530 Subject: [PATCH 2/2] feat: serialize v0 txn using VersionedTransaction from solana web3 --- .../src/operations/signTxn/index.ts | 37 +++++-------------- 1 file changed, 10 insertions(+), 27 deletions(-) diff --git a/packages/app-solana/src/operations/signTxn/index.ts b/packages/app-solana/src/operations/signTxn/index.ts index 6d946f4f..0cb33f54 100644 --- a/packages/app-solana/src/operations/signTxn/index.ts +++ b/packages/app-solana/src/operations/signTxn/index.ts @@ -103,38 +103,21 @@ export const signTxn = async ( const msgBytes = Buffer.from(params.txn, 'hex'); const isVersioned = msgBytes.length > 0 && msgBytes[0] === 0x80; + const solanaWeb3 = getSolanaWeb3(); if (isVersioned) { - const updatedMsgBytes = Buffer.from(msgBytes); - if (latestBlockHash) { - const bhBytes = Buffer.from(base58Decode(latestBlockHash)); - /* Locate blockhash in v0 message: - [0x80 version] [3 header bytes] [compact-u16 acct count] [acct keys * 32] [32 blockhash] - See: https://solana.com/docs/core/transactions/versioned-transactions */ - let off = 4; - let acctCount = 0; - let shift = 0; - while (off < updatedMsgBytes.length) { - const b = updatedMsgBytes[off]; - off += 1; - acctCount += (b % 128) * 2 ** shift; - if (b < 128) break; - shift += 7; - } - off += acctCount * 32; - bhBytes.copy(updatedMsgBytes, off); - } - - const sigCount = Buffer.from([0x01]); - const sigBytes = Buffer.from(signature, 'hex'); - const serializedTxnBuffer = Buffer.concat([ - sigCount, - sigBytes, - updatedMsgBytes, + const serializedVersionedTxn = Buffer.concat([ + Buffer.from([0x01]), + Buffer.from(signature, 'hex'), + msgBytes, ]); + const vt = solanaWeb3.VersionedTransaction.deserialize( + serializedVersionedTxn, + ); + if (latestBlockHash) vt.message.recentBlockhash = latestBlockHash; + const serializedTxnBuffer = Buffer.from(vt.serialize()); serializedTxnHex = uint8ArrayToHex(serializedTxnBuffer); serializedTxn = base58Encode(serializedTxnBuffer); } else { - const solanaWeb3 = getSolanaWeb3(); const transaction = solanaWeb3.Transaction.populate( solanaWeb3.Message.from(msgBytes), );