diff --git a/src/Solnet.Examples/Solnet.Examples.csproj b/src/Solnet.Examples/Solnet.Examples.csproj
index 7a58ac4b..1922a935 100644
--- a/src/Solnet.Examples/Solnet.Examples.csproj
+++ b/src/Solnet.Examples/Solnet.Examples.csproj
@@ -2,7 +2,7 @@
Exe
- net6.0
+ net8.0
Exe
false
diff --git a/src/Solnet.Extensions/Solnet.Extensions.csproj b/src/Solnet.Extensions/Solnet.Extensions.csproj
index 8e638a67..6bc661b6 100644
--- a/src/Solnet.Extensions/Solnet.Extensions.csproj
+++ b/src/Solnet.Extensions/Solnet.Extensions.csproj
@@ -1,7 +1,7 @@
- net6.0
+ net8.0
diff --git a/src/Solnet.KeyStore/Solnet.KeyStore.csproj b/src/Solnet.KeyStore/Solnet.KeyStore.csproj
index 77779b8f..88a0a426 100644
--- a/src/Solnet.KeyStore/Solnet.KeyStore.csproj
+++ b/src/Solnet.KeyStore/Solnet.KeyStore.csproj
@@ -1,7 +1,7 @@
- net6.0
+ net8.0
true
diff --git a/src/Solnet.Programs/ComputeBudgetProgram.cs b/src/Solnet.Programs/ComputeBudgetProgram.cs
new file mode 100644
index 00000000..2b73eb9d
--- /dev/null
+++ b/src/Solnet.Programs/ComputeBudgetProgram.cs
@@ -0,0 +1,97 @@
+using Solnet.Programs.Utilities;
+using Solnet.Rpc.Models;
+using Solnet.Wallet;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Solnet.Programs
+{
+ ///
+ /// Implements the ComputeBudget Program methods.
+ ///
+ /// For more information see: https://spl.solana.com/memo
+ ///
+ ///
+
+ public class ComputeBudgetProgram
+ {
+
+ ///
+ /// The public key of the ComputeBudget Program.
+ ///
+ public static readonly PublicKey ProgramIdKey = new("ComputeBudget111111111111111111111111111111");
+
+
+ ///
+ /// The program's name.
+ ///
+ private const string ProgramName = "Compute Budget Program";
+
+
+
+ ///
+ /// Request HeapFrame Instruction related to Priority Fees
+ ///
+ ///
+ ///
+ public static TransactionInstruction RequestHeapFrame(uint bytes)
+ {
+ List keys = new();
+
+ byte[] instructionBytes = new byte[17];
+ instructionBytes.WriteU8(1, 0);
+ instructionBytes.WriteU32(bytes, 1);
+
+ return new TransactionInstruction
+ {
+ ProgramId = ProgramIdKey.KeyBytes,
+ Keys = keys,
+ Data = instructionBytes
+ };
+ }
+ ///
+ /// Set Compute Unit Limit Instruction for Priority Fees
+ ///
+ ///
+ ///
+ public static TransactionInstruction SetComputeUnitLimit(uint units)
+ {
+ List keys = new();
+
+ byte[] instructionBytes = new byte[9];
+ instructionBytes.WriteU8(2, 0);
+ instructionBytes.WriteU64(units, 1);
+
+ return new TransactionInstruction
+ {
+ ProgramId = ProgramIdKey.KeyBytes,
+ Keys = keys,
+ Data = instructionBytes
+ };
+ }
+ ///
+ /// Set Compute Unit Price Instruction for Priority Fees
+ ///
+ ///
+ ///
+ public static TransactionInstruction SetComputeUnitPrice(ulong priority_rate)
+ {
+ List keys = new();
+
+ byte[] instructionBytes = new byte[9];
+ instructionBytes.WriteU8(3, 0);
+ instructionBytes.WriteU64(priority_rate, 1);
+
+ return new TransactionInstruction
+ {
+ ProgramId = ProgramIdKey.KeyBytes,
+ Keys = keys,
+ Data = instructionBytes
+ };
+ }
+
+ }
+}
diff --git a/src/Solnet.Programs/Solnet.Programs.csproj b/src/Solnet.Programs/Solnet.Programs.csproj
index c0f92eb4..03e2b51f 100644
--- a/src/Solnet.Programs/Solnet.Programs.csproj
+++ b/src/Solnet.Programs/Solnet.Programs.csproj
@@ -1,7 +1,7 @@
- net6.0
+ net8.0
true
diff --git a/src/Solnet.Programs/TokenProgramData.cs b/src/Solnet.Programs/TokenProgramData.cs
index 64ff2c66..24a220e4 100644
--- a/src/Solnet.Programs/TokenProgramData.cs
+++ b/src/Solnet.Programs/TokenProgramData.cs
@@ -302,7 +302,10 @@ internal static void DecodeSetAuthorityData(DecodedInstruction decodedInstructio
decodedInstruction.Values.Add("Current Authority", keys[keyIndices[1]]);
decodedInstruction.Values.Add("Authority Type", Enum.Parse(typeof(AuthorityType), data.GetU8(1).ToString()));
decodedInstruction.Values.Add("New Authority Option", data.GetU8(2));
- decodedInstruction.Values.Add("New Authority", data.GetPubKey(3));
+ if (data.Length >= 34)
+ {
+ decodedInstruction.Values.Add("New Authority", data.GetPubKey(3));
+ }
for (int i = 2; i < keyIndices.Length; i++)
{
decodedInstruction.Values.Add($"Signer {i - 1}", keys[keyIndices[i]]);
diff --git a/src/Solnet.Rpc/Builders/MessageBuilder.cs b/src/Solnet.Rpc/Builders/MessageBuilder.cs
index 289e25c4..2da82039 100644
--- a/src/Solnet.Rpc/Builders/MessageBuilder.cs
+++ b/src/Solnet.Rpc/Builders/MessageBuilder.cs
@@ -18,22 +18,22 @@ public class MessageBuilder
///
/// The length of the block hash.
///
- private const int BlockHashLength = 32;
+ protected const int BlockHashLength = 32;
///
/// The message header.
///
- private MessageHeader _messageHeader;
+ protected MessageHeader _messageHeader;
///
/// The account keys list.
///
- private readonly AccountKeysList _accountKeysList;
+ protected readonly AccountKeysList _accountKeysList;
///
/// The list of instructions contained within this transaction.
///
- internal List Instructions { get; private set; }
+ internal List Instructions { get; private protected set; }
///
/// The hash of a recent block.
@@ -76,7 +76,7 @@ internal MessageBuilder AddInstruction(TransactionInstruction instruction)
/// Builds the message into the wire format.
///
/// The encoded message.
- internal byte[] Build()
+ internal virtual byte[] Build()
{
if (RecentBlockHash == null && NonceInformation == null)
throw new Exception("recent block hash or nonce information is required");
@@ -111,8 +111,7 @@ internal byte[] Build()
{
keyIndices[i] = FindAccountIndex(keysList, instruction.Keys[i].PublicKey);
}
-
- CompiledInstruction compiledInstruction = new CompiledInstruction
+ CompiledInstruction compiledInstruction = new()
{
ProgramIdIndex = FindAccountIndex(keysList, instruction.ProgramId),
KeyIndicesCount = ShortVectorEncoding.EncodeLength(keyCount),
@@ -176,7 +175,7 @@ internal byte[] Build()
/// Gets the keys for the accounts present in the message.
///
/// The list of .
- private List GetAccountKeys()
+ protected List GetAccountKeys()
{
List newList = new();
var keysList = _accountKeysList.AccountList;
@@ -203,7 +202,7 @@ private List GetAccountKeys()
/// The .
/// The public key.
/// The index of the
- private static byte FindAccountIndex(IList accountMetas, byte[] publicKey)
+ protected static byte FindAccountIndex(IList accountMetas, byte[] publicKey)
{
string encodedKey = Encoders.Base58.EncodeData(publicKey);
return FindAccountIndex(accountMetas, encodedKey);
@@ -215,7 +214,7 @@ private static byte FindAccountIndex(IList accountMetas, byte[] pub
/// The .
/// The public key.
/// The index of the
- private static byte FindAccountIndex(IList accountMetas, string publicKey)
+ protected static byte FindAccountIndex(IList accountMetas, string publicKey)
{
for (byte index = 0; index < accountMetas.Count; index++)
{
diff --git a/src/Solnet.Rpc/Builders/VersionedMessageBuilder.cs b/src/Solnet.Rpc/Builders/VersionedMessageBuilder.cs
new file mode 100644
index 00000000..dcaa93de
--- /dev/null
+++ b/src/Solnet.Rpc/Builders/VersionedMessageBuilder.cs
@@ -0,0 +1,137 @@
+using Solnet.Rpc.Models;
+using Solnet.Rpc.Utilities;
+using Solnet.Wallet;
+using Solnet.Wallet.Utilities;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using static Solnet.Rpc.Models.Message;
+
+namespace Solnet.Rpc.Builders
+{
+ ///
+ /// A compiled instruction within the message.
+ ///
+ public class VersionedMessageBuilder : MessageBuilder
+ {
+
+ ///
+ /// Address Table Lookups
+ ///
+ public List AddressTableLookups { get; set; }
+ public IList AccountKeys { get; internal set; }
+
+ ///
+ /// Builds the message into the wire format.
+ ///
+ /// The encoded message.
+ internal override byte[] Build()
+ {
+ if (RecentBlockHash == null && NonceInformation == null)
+ throw new Exception("recent block hash or nonce information is required");
+ if (Instructions == null)
+ throw new Exception("no instructions provided in the transaction");
+
+ // In case the user specified nonce information, we'll use it.
+ if (NonceInformation != null)
+ {
+ RecentBlockHash = NonceInformation.Nonce;
+ _accountKeysList.Add(NonceInformation.Instruction.Keys);
+ _accountKeysList.Add(AccountMeta.ReadOnly(new PublicKey(NonceInformation.Instruction.ProgramId),
+ false));
+ List newInstructions = new() { NonceInformation.Instruction };
+ newInstructions.AddRange(Instructions);
+ Instructions = newInstructions;
+ }
+
+ _messageHeader = new MessageHeader();
+
+ List keysList = GetAccountKeys();
+ byte[] accountAddressesLength = ShortVectorEncoding.EncodeLength(keysList.Count);
+ int compiledInstructionsLength = 0;
+ List compiledInstructions = new();
+
+ foreach (TransactionInstruction instruction in Instructions)
+ {
+ int keyCount = instruction.Keys.Count;
+ byte[] keyIndices = new byte[keyCount];
+
+ if (instruction.GetType() == typeof(VersionedTransactionInstruction))
+ {
+ keyIndices = ((VersionedTransactionInstruction)instruction).KeyIndices;
+ }
+ else
+ {
+ for (int i = 0; i < keyCount; i++)
+ {
+ keyIndices[i] = FindAccountIndex(keysList, instruction.Keys[i].PublicKey);
+ }
+ }
+
+ CompiledInstruction compiledInstruction = new()
+ {
+ ProgramIdIndex = FindAccountIndex(keysList, instruction.ProgramId),
+ KeyIndicesCount = ShortVectorEncoding.EncodeLength(keyIndices.Length),
+ KeyIndices = keyIndices,
+ DataLength = ShortVectorEncoding.EncodeLength(instruction.Data.Length),
+ Data = instruction.Data
+ };
+ compiledInstructions.Add(compiledInstruction);
+ compiledInstructionsLength += compiledInstruction.Length();
+ }
+
+ int accountKeysBufferSize = _accountKeysList.AccountList.Count * 32;
+ MemoryStream accountKeysBuffer = new MemoryStream(accountKeysBufferSize);
+ byte[] instructionsLength = ShortVectorEncoding.EncodeLength(compiledInstructions.Count);
+
+ foreach (AccountMeta accountMeta in keysList)
+ {
+ accountKeysBuffer.Write(accountMeta.PublicKeyBytes, 0, accountMeta.PublicKeyBytes.Length);
+ if (accountMeta.IsSigner)
+ {
+ _messageHeader.RequiredSignatures += 1;
+ if (!accountMeta.IsWritable)
+ _messageHeader.ReadOnlySignedAccounts += 1;
+ }
+ else
+ {
+ if (!accountMeta.IsWritable)
+ _messageHeader.ReadOnlyUnsignedAccounts += 1;
+ }
+ }
+
+ #region Build Message Body
+
+ int messageBufferSize = MessageHeader.Layout.HeaderLength + BlockHashLength +
+ accountAddressesLength.Length +
+ +instructionsLength.Length + compiledInstructionsLength + accountKeysBufferSize;
+ MemoryStream buffer = new MemoryStream(messageBufferSize);
+ byte[] messageHeaderBytes = _messageHeader.ToBytes();
+
+ buffer.Write(new byte[] { 128 }, 0, 1);
+ buffer.Write(messageHeaderBytes, 0, messageHeaderBytes.Length);
+ buffer.Write(accountAddressesLength, 0, accountAddressesLength.Length);
+ buffer.Write(accountKeysBuffer.ToArray(), 0, accountKeysBuffer.ToArray().Length);
+ var encodedRecentBlockHash = Encoders.Base58.DecodeData(RecentBlockHash);
+ buffer.Write(encodedRecentBlockHash, 0, encodedRecentBlockHash.Length);
+ buffer.Write(instructionsLength, 0, instructionsLength.Length);
+
+ foreach (CompiledInstruction compiledInstruction in compiledInstructions)
+ {
+ buffer.WriteByte(compiledInstruction.ProgramIdIndex);
+ buffer.Write(compiledInstruction.KeyIndicesCount, 0, compiledInstruction.KeyIndicesCount.Length);
+ buffer.Write(compiledInstruction.KeyIndices, 0, compiledInstruction.KeyIndices.Length);
+ buffer.Write(compiledInstruction.DataLength, 0, compiledInstruction.DataLength.Length);
+ buffer.Write(compiledInstruction.Data, 0, compiledInstruction.Data.Length);
+ }
+
+ #endregion
+
+ var serializeAddressTableLookups = AddressTableLookupUtils.SerializeAddressTableLookups(AddressTableLookups);
+ buffer.Write(serializeAddressTableLookups, 0, serializeAddressTableLookups.Length);
+
+ return buffer.ToArray();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Solnet.Rpc/IRpcClient.cs b/src/Solnet.Rpc/IRpcClient.cs
index e3f7d0ad..391b0c1c 100644
--- a/src/Solnet.Rpc/IRpcClient.cs
+++ b/src/Solnet.Rpc/IRpcClient.cs
@@ -133,7 +133,7 @@ RequestResult> GetAccountInfo(string pubKey, Commitme
/// Whether to populate the rewards array, the default includes rewards.
/// Returns a task that holds the asynchronous operation result and state.
Task> GetBlockAsync(ulong slot, Commitment commitment = Commitment.Finalized,
- TransactionDetailsFilterType transactionDetails = TransactionDetailsFilterType.Full, bool blockRewards = false);
+ TransactionDetailsFilterType transactionDetails = TransactionDetailsFilterType.Full, bool blockRewards = false, int maxSupportedTransactionVersion = 0);
///
/// Returns identity and transaction information about a confirmed block in the ledger.
@@ -154,10 +154,11 @@ Task> GetBlockAsync(ulong slot, Commitment commitment =
/// The state commitment to consider when querying the ledger state.
/// The level of transaction detail to return, see .
/// Whether to populate the rewards array, the default includes rewards.
+ /// Max supported transaction version either LEGACY or 1
/// Returns a task that holds the asynchronous operation result and state.
[Obsolete("Please use GetBlockAsync whenever possible instead. This method is expected to be removed in solana-core v1.8.")]
Task> GetConfirmedBlockAsync(ulong slot, Commitment commitment = Commitment.Finalized,
- TransactionDetailsFilterType transactionDetails = TransactionDetailsFilterType.Full, bool blockRewards = false);
+ TransactionDetailsFilterType transactionDetails = TransactionDetailsFilterType.Full, bool blockRewards = false, int maxSupportedTransactionVersion = 0);
///
/// Returns identity and transaction information about a block in the ledger.
@@ -178,9 +179,10 @@ Task> GetConfirmedBlockAsync(ulong slot, Commitment com
/// The state commitment to consider when querying the ledger state.
/// The level of transaction detail to return, see .
/// Whether to populate the rewards array, the default includes rewards.
+ /// Max supported transaction version either LEGACY or 1
/// Returns an object that wraps the result along with possible errors with the request.
RequestResult GetBlock(ulong slot, Commitment commitment = Commitment.Finalized,
- TransactionDetailsFilterType transactionDetails = TransactionDetailsFilterType.Full, bool blockRewards = false);
+ TransactionDetailsFilterType transactionDetails = TransactionDetailsFilterType.Full, bool blockRewards = false, int maxSupportedTransactionVersion = 0);
///
/// Returns identity and transaction information about a confirmed block in the ledger.
@@ -201,10 +203,11 @@ RequestResult GetBlock(ulong slot, Commitment commitment = Commitment
/// The state commitment to consider when querying the ledger state.
/// The level of transaction detail to return, see .
/// Whether to populate the rewards array, the default includes rewards.
+ /// Max supported transaction version either LEGACY or 1
/// Returns an object that wraps the result along with possible errors with the request.
[Obsolete("Please use GetBlock whenever possible instead. This method is expected to be removed in solana-core v1.8.")]
RequestResult GetConfirmedBlock(ulong slot, Commitment commitment = Commitment.Finalized,
- TransactionDetailsFilterType transactionDetails = TransactionDetailsFilterType.Full, bool blockRewards = false);
+ TransactionDetailsFilterType transactionDetails = TransactionDetailsFilterType.Full, bool blockRewards = false, int maxSupportedTransactionVersion = 0);
///
/// Gets the block commitment of a certain block, identified by slot.
@@ -1075,9 +1078,10 @@ Task>> GetTokenSupplyAsync(string toke
///
/// Transaction signature as base-58 encoded string.
/// The state commitment to consider when querying the ledger state.
+ /// Max supported transaction version either LEGACY or 1
/// Returns a task that holds the asynchronous operation result and state.
Task> GetTransactionAsync(string signature,
- Commitment commitment = Commitment.Finalized);
+ Commitment commitment = Commitment.Finalized, int maxSupportedTransactionVersion = 0);
///
/// Returns transaction details for a confirmed transaction.
@@ -1090,9 +1094,10 @@ Task> GetTransactionAsync(string signatur
///
/// Transaction signature as base-58 encoded string.
/// The state commitment to consider when querying the ledger state.
+ /// Max supported transaction version either LEGACY or 1
/// Returns an object that wraps the result along with possible errors with the request.
[Obsolete("Please use GetTransactionAsync whenever possible instead. This method is expected to be removed in solana-core v1.8.")]
- Task> GetConfirmedTransactionAsync(string signature, Commitment commitment = Commitment.Finalized);
+ Task> GetConfirmedTransactionAsync(string signature, Commitment commitment = Commitment.Finalized, int maxSupportedTransactionVersion = 0);
///
/// Returns transaction details for a confirmed transaction.
@@ -1105,8 +1110,9 @@ Task> GetTransactionAsync(string signatur
///
/// Transaction signature as base-58 encoded string.
/// The state commitment to consider when querying the ledger state.
+ /// Max supported transaction version either LEGACY or 1
/// Returns an object that wraps the result along with possible errors with the request.
- RequestResult GetTransaction(string signature, Commitment commitment = Commitment.Finalized);
+ RequestResult GetTransaction(string signature, Commitment commitment = Commitment.Finalized, int maxSupportedTransactionVersion = 0);
///
/// Returns transaction details for a confirmed transaction.
@@ -1119,9 +1125,10 @@ Task> GetTransactionAsync(string signatur
///
/// Transaction signature as base-58 encoded string.
/// The state commitment to consider when querying the ledger state.
+ /// Max supported transaction version either LEGACY or 1
/// Returns an object that wraps the result along with possible errors with the request.
[Obsolete("Please use GetTransaction whenever possible instead. This method is expected to be removed in solana-core v1.8.")]
- RequestResult GetConfirmedTransaction(string signature, Commitment commitment = Commitment.Finalized);
+ RequestResult GetConfirmedTransaction(string signature, Commitment commitment = Commitment.Finalized, int maxSupportedTransactionVersion = 0);
///
/// Gets the total transaction count of the ledger.
diff --git a/src/Solnet.Rpc/Models/AccountKeysList.cs b/src/Solnet.Rpc/Models/AccountKeysList.cs
index b567f64a..b779423e 100644
--- a/src/Solnet.Rpc/Models/AccountKeysList.cs
+++ b/src/Solnet.Rpc/Models/AccountKeysList.cs
@@ -7,7 +7,7 @@ namespace Solnet.Rpc.Models
/// A wrapper around a list of s that takes care of deduplication and ordering according to
/// the wire format specification.
///
- internal class AccountKeysList
+ public class AccountKeysList
{
///
/// The account metas list.
diff --git a/src/Solnet.Rpc/Models/Block.cs b/src/Solnet.Rpc/Models/Block.cs
index d7df487e..bcb49137 100644
--- a/src/Solnet.Rpc/Models/Block.cs
+++ b/src/Solnet.Rpc/Models/Block.cs
@@ -39,6 +39,11 @@ public class BlockInfo
///
public long? BlockHeight { get; set; }
+ ///
+ /// Max transaction version allowed
+ ///
+ public int maxSupportedTransactionVersion { get; set; }
+
///
/// The rewards for this given block.
///
diff --git a/src/Solnet.Rpc/Models/Message.cs b/src/Solnet.Rpc/Models/Message.cs
index 49b4b113..f4a2cd35 100644
--- a/src/Solnet.Rpc/Models/Message.cs
+++ b/src/Solnet.Rpc/Models/Message.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.IO;
+using System.Linq;
namespace Solnet.Rpc.Models
{
@@ -153,6 +154,14 @@ public byte[] Serialize()
/// The Message object instance.
public static Message Deserialize(ReadOnlySpan data)
{
+
+ // Check that the message is not a VersionedMessage
+ byte prefix = data[0];
+ byte maskedPrefix = (byte)(prefix & VersionedMessage.VersionPrefixMask);
+ if (prefix != maskedPrefix)
+ throw new NotSupportedException("The message is a VersionedMessage, use VersionedMessage." +
+ "Deserialize instead.");
+
// Read message header
byte numRequiredSignatures = data[MessageHeader.Layout.RequiredSignaturesOffset];
byte numReadOnlySignedAccounts = data[MessageHeader.Layout.ReadOnlySignedAccountsOffset];
@@ -241,5 +250,244 @@ public static Message Deserialize(string data)
return Deserialize(decodedBytes);
}
+
+ public class VersionedMessage : Message
+ {
+ ///
+ /// Version prefix Mask.
+ ///
+ public const byte VersionPrefixMask = 0x7F;
+
+ ///
+ /// Address table lookup
+ ///
+ public List AddressTableLookups { get; set; }
+
+
+ ///
+ /// Deserialize a compiled message into a Message object.
+ ///
+ /// The data to deserialize into the Message object.
+ /// The Message object instance.
+ public static new VersionedMessage Deserialize(ReadOnlySpan data)
+ {
+ byte prefix = data[0];
+ byte maskedPrefix = (byte)(prefix & VersionPrefixMask);
+
+ if (prefix == maskedPrefix)
+ throw new NotSupportedException("Expected versioned message but received legacy message");
+
+ byte version = maskedPrefix;
+
+ if (version != 0)
+ throw new NotSupportedException($"Expected versioned message with version 0 but found version {version}");
+
+ data = data.Slice(1, data.Length - 1); // Remove the processed prefix byte
+
+ // Read message header
+ byte numRequiredSignatures = data[MessageHeader.Layout.RequiredSignaturesOffset];
+ byte numReadOnlySignedAccounts = data[MessageHeader.Layout.ReadOnlySignedAccountsOffset];
+ byte numReadOnlyUnsignedAccounts = data[MessageHeader.Layout.ReadOnlyUnsignedAccountsOffset];
+
+ // Read account keys
+ (int accountAddressLength, int accountAddressLengthEncodedLength) =
+ ShortVectorEncoding.DecodeLength(data.Slice(MessageHeader.Layout.HeaderLength,
+ ShortVectorEncoding.SpanLength));
+ List accountKeys = new(accountAddressLength);
+ for (int i = 0; i < accountAddressLength; i++)
+ {
+ ReadOnlySpan keyBytes = data.Slice(
+ MessageHeader.Layout.HeaderLength + accountAddressLengthEncodedLength +
+ i * PublicKey.PublicKeyLength,
+ PublicKey.PublicKeyLength);
+ accountKeys.Add(new PublicKey(keyBytes));
+ }
+
+ // Read block hash
+ string blockHash =
+ Encoders.Base58.EncodeData(data.Slice(
+ MessageHeader.Layout.HeaderLength + accountAddressLengthEncodedLength +
+ accountAddressLength * PublicKey.PublicKeyLength,
+ PublicKey.PublicKeyLength).ToArray());
+
+ // Read the number of instructions in the message
+ (int instructionsLength, int instructionsLengthEncodedLength) =
+ ShortVectorEncoding.DecodeLength(
+ data.Slice(
+ MessageHeader.Layout.HeaderLength + accountAddressLengthEncodedLength +
+ (accountAddressLength * PublicKey.PublicKeyLength) + PublicKey.PublicKeyLength,
+ ShortVectorEncoding.SpanLength));
+
+ List instructions = new(instructionsLength);
+ int instructionsOffset =
+ MessageHeader.Layout.HeaderLength + accountAddressLengthEncodedLength +
+ (accountAddressLength * PublicKey.PublicKeyLength) + PublicKey.PublicKeyLength +
+ instructionsLengthEncodedLength;
+ ReadOnlySpan instructionsData = data[instructionsOffset..];
+
+ int instructionsDataLength = 0;
+
+ // Read the instructions in the message
+ for (int i = 0; i < instructionsLength; i++)
+ {
+ (CompiledInstruction compiledInstruction, int instructionLength) =
+ CompiledInstruction.Deserialize(instructionsData);
+ instructions.Add(compiledInstruction);
+ instructionsData = instructionsData[instructionLength..];
+ instructionsDataLength += instructionLength;
+ }
+
+ // Read the address table lookups
+ int tableLookupOffset =
+ MessageHeader.Layout.HeaderLength + accountAddressLengthEncodedLength +
+ (accountAddressLength * PublicKey.PublicKeyLength) + PublicKey.PublicKeyLength +
+ instructionsLengthEncodedLength + instructionsDataLength;
+
+ ReadOnlySpan tableLookupData = data[tableLookupOffset..];
+
+ (int addressTableLookupsCount, int addressTableLookupsEncodedCount) = ShortVectorEncoding.DecodeLength(tableLookupData);
+ List addressTableLookups = new();
+ tableLookupData = tableLookupData[addressTableLookupsEncodedCount..];
+
+ for (int i = 0; i < addressTableLookupsCount; i++)
+ {
+ byte[] accountKeyBytes = tableLookupData.Slice(0, PublicKey.PublicKeyLength).ToArray();
+ PublicKey accountKey = new(accountKeyBytes);
+ tableLookupData = tableLookupData.Slice(PublicKey.PublicKeyLength);
+
+ (int writableIndexesLength, int writableIndexesEncodedLength) = ShortVectorEncoding.DecodeLength(tableLookupData);
+ List writableIndexes = tableLookupData.Slice(writableIndexesEncodedLength, writableIndexesLength).ToArray().ToList();
+ tableLookupData = tableLookupData.Slice(writableIndexesEncodedLength + writableIndexesLength);
+
+ (int readonlyIndexesLength, int readonlyIndexesEncodedLength) = ShortVectorEncoding.DecodeLength(tableLookupData);
+ List readonlyIndexes = tableLookupData.Slice(readonlyIndexesEncodedLength, readonlyIndexesLength).ToArray().ToList();
+ tableLookupData = tableLookupData.Slice(readonlyIndexesEncodedLength + readonlyIndexesLength);
+
+ addressTableLookups.Add(new MessageAddressTableLookup
+ {
+ AccountKey = accountKey,
+ WritableIndexes = writableIndexes.ToArray(),
+ ReadonlyIndexes = readonlyIndexes.ToArray()
+ });
+ }
+
+ return new VersionedMessage()
+ {
+ Header = new MessageHeader()
+ {
+ RequiredSignatures = numRequiredSignatures,
+ ReadOnlySignedAccounts = numReadOnlySignedAccounts,
+ ReadOnlyUnsignedAccounts = numReadOnlyUnsignedAccounts
+ },
+ RecentBlockhash = blockHash,
+ AccountKeys = accountKeys,
+ Instructions = instructions,
+ AddressTableLookups = addressTableLookups
+ };
+ }
+
+ ///
+ /// Deserialize a compiled message encoded as base-64 into a Message object.
+ ///
+ /// The data to deserialize into the Message object.
+ /// The Transaction object.
+ /// Thrown when the given string is null.
+ public static new VersionedMessage Deserialize(string data)
+ {
+ if (data == null)
+ throw new ArgumentNullException(nameof(data));
+
+ byte[] decodedBytes;
+
+ try
+ {
+ decodedBytes = Convert.FromBase64String(data);
+ }
+ catch (Exception ex)
+ {
+ throw new Exception("could not decode message data from base64", ex);
+ }
+
+ return Deserialize(decodedBytes);
+ }
+
+ ///
+ /// Deserialize the message version
+ ///
+ ///
+ ///
+ public static string DeserializeMessageVersion(byte[] serializedMessage)
+ {
+ byte prefix = serializedMessage[0];
+ byte maskedPrefix = (byte)(prefix & VersionPrefixMask);
+
+ // If the highest bit of the prefix is not set, the message is not versioned
+ if (maskedPrefix == prefix)
+ {
+ return "legacy";
+ }
+
+ // The lower 7 bits of the prefix indicate the message version
+ return maskedPrefix.ToString();
+ }
+ }
+
+ ///
+ /// Message Address Lookup table
+ ///
+ public class MessageAddressTableLookup
+ {
+ ///
+ /// Account Key
+ ///
+ public PublicKey AccountKey { get; set; }
+
+ ///
+ /// Writable indexes
+ ///
+ public byte[] WritableIndexes { get; set; }
+
+ ///
+ /// Read only indexes
+ ///
+ public byte[] ReadonlyIndexes { get; set; }
+ }
+
+ ///
+ /// Message Address Lookup table
+ ///
+ public static class AddressTableLookupUtils
+ {
+ ///
+ /// Serialize the address table lookups
+ ///
+ ///
+ ///
+ public static byte[] SerializeAddressTableLookups(List addressTableLookups)
+ {
+ MemoryStream buffer = new();
+
+ var encodedAddressTableLookupsLength = ShortVectorEncoding.EncodeLength(addressTableLookups.Count);
+ buffer.Write(encodedAddressTableLookupsLength, 0, encodedAddressTableLookupsLength.Length);
+
+ foreach (var lookup in addressTableLookups)
+ {
+ // Write the Account Key
+ buffer.Write(lookup.AccountKey, 0, PublicKey.PublicKeyLength);
+
+ // Write the Writable Indexes
+ var encodedWritableIndexesLength = ShortVectorEncoding.EncodeLength(lookup.WritableIndexes.Length);
+ buffer.Write(encodedWritableIndexesLength, 0, encodedWritableIndexesLength.Length);
+ buffer.Write(lookup.WritableIndexes, 0, lookup.WritableIndexes.Length);
+
+ // Write the Readonly Indexes
+ var encodedReadonlyIndexesLength = ShortVectorEncoding.EncodeLength(lookup.ReadonlyIndexes.Length);
+ buffer.Write(encodedReadonlyIndexesLength, 0, encodedReadonlyIndexesLength.Length);
+ buffer.Write(lookup.ReadonlyIndexes, 0, lookup.ReadonlyIndexes.Length);
+ }
+
+ return buffer.ToArray();
+ }
+ }
}
-}
\ No newline at end of file
+}
diff --git a/src/Solnet.Rpc/Models/Transaction.cs b/src/Solnet.Rpc/Models/Transaction.cs
index a433c53e..4e3a3fcd 100644
--- a/src/Solnet.Rpc/Models/Transaction.cs
+++ b/src/Solnet.Rpc/Models/Transaction.cs
@@ -6,6 +6,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using static Solnet.Rpc.Models.Message;
namespace Solnet.Rpc.Models
{
@@ -61,6 +62,8 @@ public class Transaction
///
public string RecentBlockHash { get; set; }
+
+ internal IList _accountKeys;
///
/// The nonce information of the transaction.
///
@@ -81,7 +84,7 @@ public class Transaction
///
/// Compile the transaction data.
///
- public byte[] CompileMessage()
+ public virtual byte[] CompileMessage()
{
MessageBuilder messageBuilder = new() { FeePayer = FeePayer };
@@ -374,6 +377,15 @@ public static Transaction Deserialize(ReadOnlySpan data)
signatures.Add(signature.ToArray());
}
+
+ byte prefix = data[encodedLength + (signaturesLength * TransactionBuilder.SignatureLength)];
+ byte maskedPrefix = (byte)(prefix & VersionedMessage.VersionPrefixMask);
+
+ // If the transaction is a VersionedTransaction, use VersionedTransaction.Deserialize instead.
+ if (prefix != maskedPrefix)
+ return VersionedTransaction.Deserialize(data);
+
+
return Populate(
Message.Deserialize(data[
(encodedLength + (signaturesLength * TransactionBuilder.SignatureLength))..]),
diff --git a/src/Solnet.Rpc/Models/TransactionInstruction.cs b/src/Solnet.Rpc/Models/TransactionInstruction.cs
index 480565d6..e1e52210 100644
--- a/src/Solnet.Rpc/Models/TransactionInstruction.cs
+++ b/src/Solnet.Rpc/Models/TransactionInstruction.cs
@@ -4,6 +4,16 @@
namespace Solnet.Rpc.Models
{
+ ///
+ /// Represents a versioned transaction instruction before being compiled into the transaction's message.
+ ///
+ public class VersionedTransactionInstruction : TransactionInstruction
+ {
+ ///
+ /// The keys associated with the instruction.
+ ///
+ public byte[] KeyIndices { get; init; }
+ }
///
/// Represents a transaction instruction before being compiled into the transaction's message.
///
diff --git a/src/Solnet.Rpc/Models/VersionedTransaction.cs b/src/Solnet.Rpc/Models/VersionedTransaction.cs
new file mode 100644
index 00000000..0b7424c9
--- /dev/null
+++ b/src/Solnet.Rpc/Models/VersionedTransaction.cs
@@ -0,0 +1,169 @@
+using Solnet.Rpc.Builders;
+using Solnet.Rpc.Utilities;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using static Solnet.Rpc.Models.Message;
+
+namespace Solnet.Rpc.Models
+{
+ ///
+ /// Represents a Transaction in Solana.
+ ///
+ public class VersionedTransaction : Transaction
+ {
+
+ ///
+ /// Address Table Lookups
+ ///
+ public List AddressTableLookups { get; set; }
+
+
+ ///
+ /// Compile the transaction data.
+ ///
+ public override byte[] CompileMessage()
+ {
+ VersionedMessageBuilder messageBuilder = new() { FeePayer = FeePayer, AccountKeys = _accountKeys };
+
+ if (RecentBlockHash != null) messageBuilder.RecentBlockHash = RecentBlockHash;
+ if (NonceInformation != null) messageBuilder.NonceInformation = NonceInformation;
+
+ foreach (TransactionInstruction instruction in Instructions)
+ {
+ messageBuilder.AddInstruction(instruction);
+ }
+
+ messageBuilder.AddressTableLookups = AddressTableLookups;
+
+ return messageBuilder.Build();
+ }
+
+ ///
+ /// Populate the Transaction from the given message and signatures.
+ ///
+ /// The object.
+ /// The list of signatures.
+ /// The Transaction object.
+ public static VersionedTransaction Populate(VersionedMessage message, IList signatures = null)
+ {
+ VersionedTransaction tx = new()
+ {
+ RecentBlockHash = message.RecentBlockhash,
+ Signatures = new List(),
+ Instructions = new List(),
+ AddressTableLookups = message.AddressTableLookups,
+ _accountKeys = message.AccountKeys
+ };
+
+ if (message.Header.RequiredSignatures > 0)
+ {
+ tx.FeePayer = message.AccountKeys[0];
+ }
+
+ if (signatures != null)
+ {
+ for (int i = 0; i < signatures.Count; i++)
+ {
+ tx.Signatures.Add(new SignaturePubKeyPair
+ {
+ PublicKey = message.AccountKeys[i],
+ Signature = signatures[i]
+ });
+ }
+ }
+
+ for (int i = 0; i < message.Instructions.Count; i++)
+ {
+ CompiledInstruction compiledInstruction = message.Instructions[i];
+ (int accountLength, _) = ShortVectorEncoding.DecodeLength(compiledInstruction.KeyIndicesCount);
+
+ List accounts = new(accountLength);
+ for (int j = 0; j < accountLength; j++)
+ {
+ int k = compiledInstruction.KeyIndices[j];
+ if (k >= message.AccountKeys.Count) continue;
+ accounts.Add(new AccountMeta(message.AccountKeys[k], message.IsAccountWritable(k),
+ tx.Signatures.Any(pair => pair.PublicKey.Key == message.AccountKeys[k].Key) || message.IsAccountSigner(k)));
+ }
+
+ VersionedTransactionInstruction instruction = new()
+ {
+ Keys = accounts,
+ KeyIndices = compiledInstruction.KeyIndices,
+ ProgramId = message.AccountKeys[compiledInstruction.ProgramIdIndex],
+ Data = compiledInstruction.Data
+ };
+ if (i == 0 && accounts.Any(a => a.PublicKey == "SysvarRecentB1ockHashes11111111111111111111"))
+ {
+ tx.NonceInformation = new NonceInformation { Instruction = instruction, Nonce = tx.RecentBlockHash };
+ continue;
+ }
+ tx.Instructions.Add(instruction);
+ }
+
+ return tx;
+ }
+
+ ///
+ /// Populate the Transaction from the given compiled message and signatures.
+ ///
+ /// The compiled message, as base-64 encoded string.
+ /// The list of signatures.
+ /// The Transaction object.
+ public static new VersionedTransaction Populate(string message, IList signatures = null)
+ => Populate(VersionedMessage.Deserialize(message), signatures);
+
+ ///
+ /// Deserialize a wire format transaction into a Transaction object.
+ ///
+ /// The data to deserialize into the Transaction object.
+ /// The Transaction object.
+ public static new VersionedTransaction Deserialize(ReadOnlySpan data)
+ {
+ // Read number of signatures
+ (int signaturesLength, int encodedLength) =
+ ShortVectorEncoding.DecodeLength(data[..ShortVectorEncoding.SpanLength]);
+ List signatures = new(signaturesLength);
+
+ for (int i = 0; i < signaturesLength; i++)
+ {
+ ReadOnlySpan signature =
+ data.Slice(encodedLength + (i * TransactionBuilder.SignatureLength),
+ TransactionBuilder.SignatureLength);
+ signatures.Add(signature.ToArray());
+ }
+
+ var message = VersionedMessage.Deserialize(data[(encodedLength + (signaturesLength * TransactionBuilder.SignatureLength))..]);
+ return Populate(message, signatures);
+ }
+
+ ///
+ /// Deserialize a transaction encoded as base-64 into a Transaction object.
+ ///
+ /// The data to deserialize into the Transaction object.
+ /// The Transaction object.
+ /// Thrown when the given string is null.
+ public static new VersionedTransaction Deserialize(string data)
+ {
+ if (data == null)
+ throw new ArgumentNullException(nameof(data));
+
+ byte[] decodedBytes;
+
+ try
+ {
+ decodedBytes = Convert.FromBase64String(data);
+ }
+ catch (Exception ex)
+ {
+ throw new Exception("could not decode transaction data from base64", ex);
+ }
+
+ return Deserialize(decodedBytes);
+ }
+ }
+}
+
diff --git a/src/Solnet.Rpc/SolanaRpcClient.cs b/src/Solnet.Rpc/SolanaRpcClient.cs
index b24a1235..af53de30 100644
--- a/src/Solnet.Rpc/SolanaRpcClient.cs
+++ b/src/Solnet.Rpc/SolanaRpcClient.cs
@@ -211,7 +211,7 @@ public RequestResult> GetBalance(string pubKey,
public async Task> GetBlockAsync(ulong slot,
Commitment commitment = Commitment.Finalized,
TransactionDetailsFilterType transactionDetails = TransactionDetailsFilterType.Full,
- bool blockRewards = false)
+ bool blockRewards = false, int maxSupportedTransactionVersion = 0)
{
if (commitment == Commitment.Processed)
{
@@ -221,6 +221,7 @@ public async Task> GetBlockAsync(ulong slot,
return await SendRequestAsync("getBlock",
Parameters.Create(slot, ConfigObject.Create(
KeyValue.Create("encoding", "json"),
+ KeyValue.Create("maxSupportedTransactionVersion", maxSupportedTransactionVersion),
HandleTransactionDetails(transactionDetails),
KeyValue.Create("rewards", blockRewards ? blockRewards : null),
HandleCommitment(commitment))));
@@ -229,8 +230,8 @@ public async Task> GetBlockAsync(ulong slot,
///
public RequestResult GetBlock(ulong slot, Commitment commitment = Commitment.Finalized,
TransactionDetailsFilterType transactionDetails = TransactionDetailsFilterType.Full,
- bool blockRewards = false)
- => GetBlockAsync(slot, commitment, transactionDetails, blockRewards).Result;
+ bool blockRewards = false, int maxSupportedTransactionVersion = 0)
+ => GetBlockAsync(slot, commitment, transactionDetails, blockRewards, maxSupportedTransactionVersion).Result;
///
@@ -251,7 +252,7 @@ public async Task>> GetBlocksAsync(ulong startSlot, ul
public async Task> GetConfirmedBlockAsync(ulong slot,
Commitment commitment = Commitment.Finalized,
TransactionDetailsFilterType transactionDetails = TransactionDetailsFilterType.Full,
- bool blockRewards = false)
+ bool blockRewards = false, int maxSupportedTransactionVersion = 0)
{
if (commitment == Commitment.Processed)
{
@@ -261,6 +262,7 @@ public async Task> GetConfirmedBlockAsync(ulong slot,
return await SendRequestAsync("getConfirmedBlock",
Parameters.Create(slot, ConfigObject.Create(
KeyValue.Create("encoding", "json"),
+ KeyValue.Create("maxSupportedTransactionVersion", maxSupportedTransactionVersion),
HandleTransactionDetails(transactionDetails),
KeyValue.Create("rewards", blockRewards ? blockRewards : null),
HandleCommitment(commitment))));
@@ -269,7 +271,7 @@ public async Task> GetConfirmedBlockAsync(ulong slot,
///
public RequestResult GetConfirmedBlock(ulong slot, Commitment commitment = Commitment.Finalized,
TransactionDetailsFilterType transactionDetails = TransactionDetailsFilterType.Full,
- bool blockRewards = false)
+ bool blockRewards = false, int maxSupportedTransactionVersion = 0)
=> GetConfirmedBlockAsync(slot, commitment, transactionDetails, blockRewards).Result;
@@ -425,31 +427,31 @@ public async Task>>> GetLeaderSched
///
public async Task> GetTransactionAsync(string signature,
- Commitment commitment = Commitment.Finalized)
+ Commitment commitment = Commitment.Finalized, int maxSupportedTransactionVersion = 0)
{
return await SendRequestAsync("getTransaction",
Parameters.Create(signature,
- ConfigObject.Create(KeyValue.Create("encoding", "json"), HandleCommitment(commitment))));
+ ConfigObject.Create(KeyValue.Create("encoding", "json"), HandleCommitment(commitment), KeyValue.Create("maxSupportedTransactionVersion", maxSupportedTransactionVersion))));
}
- ///
+ ///
public async Task> GetConfirmedTransactionAsync(string signature,
- Commitment commitment = Commitment.Finalized)
+ Commitment commitment = Commitment.Finalized, int maxSupportedTransactionVersion = 0)
{
return await SendRequestAsync("getConfirmedTransaction",
Parameters.Create(signature,
- ConfigObject.Create(KeyValue.Create("encoding", "json"), HandleCommitment(commitment))));
+ ConfigObject.Create(KeyValue.Create("encoding", "json"), HandleCommitment(commitment), KeyValue.Create("maxSupportedTransactionVersion", maxSupportedTransactionVersion))));
}
///
public RequestResult GetTransaction(string signature,
- Commitment commitment = Commitment.Finalized)
- => GetTransactionAsync(signature, commitment).Result;
+ Commitment commitment = Commitment.Finalized, int maxSupportedTransactionVersion = 0)
+ => GetTransactionAsync(signature, commitment, maxSupportedTransactionVersion).Result;
- ///
+ ///
public RequestResult GetConfirmedTransaction(string signature,
- Commitment commitment = Commitment.Finalized) =>
- GetConfirmedTransactionAsync(signature, commitment).Result;
+ Commitment commitment = Commitment.Finalized, int maxSupportedTransactionVersion = 0) =>
+ GetConfirmedTransactionAsync(signature, commitment, maxSupportedTransactionVersion).Result;
///
public async Task> GetBlockHeightAsync(Commitment commitment = Commitment.Finalized)
diff --git a/src/Solnet.Rpc/Solnet.Rpc.csproj b/src/Solnet.Rpc/Solnet.Rpc.csproj
index 0136dedc..0780042a 100644
--- a/src/Solnet.Rpc/Solnet.Rpc.csproj
+++ b/src/Solnet.Rpc/Solnet.Rpc.csproj
@@ -1,7 +1,7 @@
- net6.0
+ net8.0
Solnet.Rpc
Solnet.Rpc
diff --git a/src/Solnet.Wallet/Account.cs b/src/Solnet.Wallet/Account.cs
index ddadfb2e..4e8ffde2 100644
--- a/src/Solnet.Wallet/Account.cs
+++ b/src/Solnet.Wallet/Account.cs
@@ -1,5 +1,6 @@
using Chaos.NaCl;
using Solnet.Wallet.Utilities;
+using System;
using System.Diagnostics;
namespace Solnet.Wallet
@@ -50,7 +51,23 @@ public Account(byte[] privateKey, byte[] publicKey)
PrivateKey = new PrivateKey(privateKey);
PublicKey = new PublicKey(publicKey);
}
+ ///
+ /// Initialize an account with the passed secret key
+ ///
+ /// The private key.
+ public static Account FromSecretKey(string secretKey)
+ {
+ var B58 = new Base58Encoder();
+ byte[] skeyBytes = B58.DecodeData(secretKey);
+ if (skeyBytes.Length != 64)
+ {
+ throw new ArgumentException("Not a secret key");
+ }
+ Account acc = new Account(skeyBytes, skeyBytes.Slice(32, 64));
+
+ return acc;
+ }
///
/// Verify the signed message.
///
diff --git a/test/Solnet.Extensions.Test/Solnet.Extensions.Test.csproj b/test/Solnet.Extensions.Test/Solnet.Extensions.Test.csproj
index ff579957..5a17f6d0 100644
--- a/test/Solnet.Extensions.Test/Solnet.Extensions.Test.csproj
+++ b/test/Solnet.Extensions.Test/Solnet.Extensions.Test.csproj
@@ -1,7 +1,7 @@
- net6.0
+ net8.0
false
diff --git a/test/Solnet.KeyStore.Test/Solnet.KeyStore.Test.csproj b/test/Solnet.KeyStore.Test/Solnet.KeyStore.Test.csproj
index ac340158..7b6212e5 100644
--- a/test/Solnet.KeyStore.Test/Solnet.KeyStore.Test.csproj
+++ b/test/Solnet.KeyStore.Test/Solnet.KeyStore.Test.csproj
@@ -1,7 +1,7 @@
- net6.0
+ net8.0
false
diff --git a/test/Solnet.Programs.Test/Solnet.Programs.Test.csproj b/test/Solnet.Programs.Test/Solnet.Programs.Test.csproj
index 1a6ae3c6..e26cddd6 100644
--- a/test/Solnet.Programs.Test/Solnet.Programs.Test.csproj
+++ b/test/Solnet.Programs.Test/Solnet.Programs.Test.csproj
@@ -1,7 +1,7 @@
- net6.0
+ net8.0
false
diff --git a/test/Solnet.Rpc.Test/Resources/Http/Blocks/GetBlockConfirmedRequest.json b/test/Solnet.Rpc.Test/Resources/Http/Blocks/GetBlockConfirmedRequest.json
index a7f4a162..9ba5920a 100644
--- a/test/Solnet.Rpc.Test/Resources/Http/Blocks/GetBlockConfirmedRequest.json
+++ b/test/Solnet.Rpc.Test/Resources/Http/Blocks/GetBlockConfirmedRequest.json
@@ -1 +1 @@
-{"method":"getBlock","params":[79662905,{"encoding":"json","commitment":"confirmed"}],"jsonrpc":"2.0","id":0}
\ No newline at end of file
+{"method":"getBlock","params":[79662905,{"encoding":"json","maxSupportedTransactionVersion":0,"commitment":"confirmed"}],"jsonrpc":"2.0","id":0}
\ No newline at end of file
diff --git a/test/Solnet.Rpc.Test/Resources/Http/Blocks/GetBlockRequest.json b/test/Solnet.Rpc.Test/Resources/Http/Blocks/GetBlockRequest.json
index 938aad92..a96d097e 100644
--- a/test/Solnet.Rpc.Test/Resources/Http/Blocks/GetBlockRequest.json
+++ b/test/Solnet.Rpc.Test/Resources/Http/Blocks/GetBlockRequest.json
@@ -1 +1 @@
-{"method":"getBlock","params":[79662905,{"encoding":"json"}],"jsonrpc":"2.0","id":0}
\ No newline at end of file
+{"method":"getBlock","params":[79662905,{"encoding":"json","maxSupportedTransactionVersion":0}],"jsonrpc":"2.0","id":0}
\ No newline at end of file
diff --git a/test/Solnet.Rpc.Test/Resources/Http/Blocks/GetConfirmedBlockConfirmedRequest.json b/test/Solnet.Rpc.Test/Resources/Http/Blocks/GetConfirmedBlockConfirmedRequest.json
index aa11cfe0..37835143 100644
--- a/test/Solnet.Rpc.Test/Resources/Http/Blocks/GetConfirmedBlockConfirmedRequest.json
+++ b/test/Solnet.Rpc.Test/Resources/Http/Blocks/GetConfirmedBlockConfirmedRequest.json
@@ -1 +1 @@
-{"method":"getConfirmedBlock","params":[79662905,{"encoding":"json","commitment":"confirmed"}],"jsonrpc":"2.0","id":0}
\ No newline at end of file
+{"method":"getConfirmedBlock","params":[79662905,{"encoding":"json","maxSupportedTransactionVersion":0,"commitment":"confirmed"}],"jsonrpc":"2.0","id":0}
\ No newline at end of file
diff --git a/test/Solnet.Rpc.Test/Resources/Http/Blocks/GetConfirmedBlockRequest.json b/test/Solnet.Rpc.Test/Resources/Http/Blocks/GetConfirmedBlockRequest.json
index cc090029..d7d6d32c 100644
--- a/test/Solnet.Rpc.Test/Resources/Http/Blocks/GetConfirmedBlockRequest.json
+++ b/test/Solnet.Rpc.Test/Resources/Http/Blocks/GetConfirmedBlockRequest.json
@@ -1 +1 @@
-{"method":"getConfirmedBlock","params":[79662905,{"encoding":"json"}],"jsonrpc":"2.0","id":0}
\ No newline at end of file
+{"method":"getConfirmedBlock","params":[79662905,{"encoding":"json","maxSupportedTransactionVersion":0}],"jsonrpc":"2.0","id":0}
\ No newline at end of file
diff --git a/test/Solnet.Rpc.Test/Resources/Http/Transaction/GetConfirmedTransactionProcessedRequest.json b/test/Solnet.Rpc.Test/Resources/Http/Transaction/GetConfirmedTransactionProcessedRequest.json
index 62c95d8c..66cf3975 100644
--- a/test/Solnet.Rpc.Test/Resources/Http/Transaction/GetConfirmedTransactionProcessedRequest.json
+++ b/test/Solnet.Rpc.Test/Resources/Http/Transaction/GetConfirmedTransactionProcessedRequest.json
@@ -1 +1 @@
-{"method":"getConfirmedTransaction","params":["5as3w4KMpY23MP5T1nkPVksjXjN7hnjHKqiDxRMxUNcw5XsCGtStayZib1kQdyR2D9w8dR11Ha9Xk38KP3kbAwM1",{"encoding":"json","commitment":"processed"}],"jsonrpc":"2.0","id":0}
\ No newline at end of file
+{"method":"getConfirmedTransaction","params":["5as3w4KMpY23MP5T1nkPVksjXjN7hnjHKqiDxRMxUNcw5XsCGtStayZib1kQdyR2D9w8dR11Ha9Xk38KP3kbAwM1",{"encoding":"json","commitment":"processed","maxSupportedTransactionVersion":0}],"jsonrpc":"2.0","id":0}
\ No newline at end of file
diff --git a/test/Solnet.Rpc.Test/Resources/Http/Transaction/GetConfirmedTransactionRequest.json b/test/Solnet.Rpc.Test/Resources/Http/Transaction/GetConfirmedTransactionRequest.json
index b5b11244..8d531c08 100644
--- a/test/Solnet.Rpc.Test/Resources/Http/Transaction/GetConfirmedTransactionRequest.json
+++ b/test/Solnet.Rpc.Test/Resources/Http/Transaction/GetConfirmedTransactionRequest.json
@@ -1 +1 @@
-{"method":"getConfirmedTransaction","params":["5as3w4KMpY23MP5T1nkPVksjXjN7hnjHKqiDxRMxUNcw5XsCGtStayZib1kQdyR2D9w8dR11Ha9Xk38KP3kbAwM1",{"encoding":"json","commitment":"confirmed"}],"jsonrpc":"2.0","id":0}
\ No newline at end of file
+{"method":"getConfirmedTransaction","params":["5as3w4KMpY23MP5T1nkPVksjXjN7hnjHKqiDxRMxUNcw5XsCGtStayZib1kQdyR2D9w8dR11Ha9Xk38KP3kbAwM1",{"encoding":"json","commitment":"confirmed","maxSupportedTransactionVersion":0}],"jsonrpc":"2.0","id":0}
\ No newline at end of file
diff --git a/test/Solnet.Rpc.Test/Resources/Http/Transaction/GetTransactionProcessedRequest.json b/test/Solnet.Rpc.Test/Resources/Http/Transaction/GetTransactionProcessedRequest.json
index 872a41f3..0a81d834 100644
--- a/test/Solnet.Rpc.Test/Resources/Http/Transaction/GetTransactionProcessedRequest.json
+++ b/test/Solnet.Rpc.Test/Resources/Http/Transaction/GetTransactionProcessedRequest.json
@@ -1 +1 @@
-{"method":"getTransaction","params":["5as3w4KMpY23MP5T1nkPVksjXjN7hnjHKqiDxRMxUNcw5XsCGtStayZib1kQdyR2D9w8dR11Ha9Xk38KP3kbAwM1",{"encoding":"json","commitment":"processed"}],"jsonrpc":"2.0","id":0}
\ No newline at end of file
+{"method":"getTransaction","params":["5as3w4KMpY23MP5T1nkPVksjXjN7hnjHKqiDxRMxUNcw5XsCGtStayZib1kQdyR2D9w8dR11Ha9Xk38KP3kbAwM1",{"encoding":"json","commitment":"processed","maxSupportedTransactionVersion":0}],"jsonrpc":"2.0","id":0}
\ No newline at end of file
diff --git a/test/Solnet.Rpc.Test/Resources/Http/Transaction/GetTransactionRequest.json b/test/Solnet.Rpc.Test/Resources/Http/Transaction/GetTransactionRequest.json
index 230a7558..76400a00 100644
--- a/test/Solnet.Rpc.Test/Resources/Http/Transaction/GetTransactionRequest.json
+++ b/test/Solnet.Rpc.Test/Resources/Http/Transaction/GetTransactionRequest.json
@@ -1 +1 @@
-{"method":"getTransaction","params":["5as3w4KMpY23MP5T1nkPVksjXjN7hnjHKqiDxRMxUNcw5XsCGtStayZib1kQdyR2D9w8dR11Ha9Xk38KP3kbAwM1",{"encoding":"json"}],"jsonrpc":"2.0","id":0}
\ No newline at end of file
+{"method":"getTransaction","params":["5as3w4KMpY23MP5T1nkPVksjXjN7hnjHKqiDxRMxUNcw5XsCGtStayZib1kQdyR2D9w8dR11Ha9Xk38KP3kbAwM1",{"encoding":"json","maxSupportedTransactionVersion":0}],"jsonrpc":"2.0","id":0}
\ No newline at end of file
diff --git a/test/Solnet.Rpc.Test/Resources/Http/Transaction/GetTransactionRequest2.json b/test/Solnet.Rpc.Test/Resources/Http/Transaction/GetTransactionRequest2.json
index 43b9b7a7..cd04da7b 100644
--- a/test/Solnet.Rpc.Test/Resources/Http/Transaction/GetTransactionRequest2.json
+++ b/test/Solnet.Rpc.Test/Resources/Http/Transaction/GetTransactionRequest2.json
@@ -1 +1 @@
-{"method":"getTransaction","params":["3Q9mu4ePvtbtQzY1kpGmaViJKyBev6hgUppyXDF9hKgWHHnecwGLE2pSoFvNUF3h7acKyFwWd65bkwr9A1jN2CdT",{"encoding":"json"}],"jsonrpc":"2.0","id":0}
\ No newline at end of file
+{"method":"getTransaction","params":["3Q9mu4ePvtbtQzY1kpGmaViJKyBev6hgUppyXDF9hKgWHHnecwGLE2pSoFvNUF3h7acKyFwWd65bkwr9A1jN2CdT",{"encoding":"json","maxSupportedTransactionVersion":0}],"jsonrpc":"2.0","id":0}
\ No newline at end of file
diff --git a/test/Solnet.Rpc.Test/SolanaRpcClientBlockTests.cs b/test/Solnet.Rpc.Test/SolanaRpcClientBlockTests.cs
index f69f2bc2..76c766a4 100644
--- a/test/Solnet.Rpc.Test/SolanaRpcClientBlockTests.cs
+++ b/test/Solnet.Rpc.Test/SolanaRpcClientBlockTests.cs
@@ -113,7 +113,7 @@ public void TestGetConfirmedBlock()
Assert.AreEqual(79662904UL, res.Result.ParentSlot);
Assert.AreEqual("5wLhsKAH9SCPbRZc4qWf3GBiod9CD8sCEZfMiU25qW8", res.Result.Blockhash);
Assert.AreEqual("CjJ97j84mUq3o67CEqzEkTifXpHLBCD8GvmfBYLz4Zdg", res.Result.PreviousBlockhash);
-
+ Assert.AreEqual(0, res.Result.maxSupportedTransactionVersion);
Assert.AreEqual(1, res.Result.Rewards.Length);
var rewards = res.Result.Rewards[0];
diff --git a/test/Solnet.Rpc.Test/Solnet.Rpc.Test.csproj b/test/Solnet.Rpc.Test/Solnet.Rpc.Test.csproj
index 97c76f60..d9cd4ef9 100644
--- a/test/Solnet.Rpc.Test/Solnet.Rpc.Test.csproj
+++ b/test/Solnet.Rpc.Test/Solnet.Rpc.Test.csproj
@@ -1,7 +1,7 @@
- net6.0
+ net8.0
false
diff --git a/test/Solnet.Wallet.Test/Solnet.Wallet.Test.csproj b/test/Solnet.Wallet.Test/Solnet.Wallet.Test.csproj
index e74856d9..aee271d8 100644
--- a/test/Solnet.Wallet.Test/Solnet.Wallet.Test.csproj
+++ b/test/Solnet.Wallet.Test/Solnet.Wallet.Test.csproj
@@ -1,7 +1,7 @@
- net6.0
+ net8.0
false