From 660e896c5f2cf6ffb19e0b7b77e3d7004e77b383 Mon Sep 17 00:00:00 2001 From: murlux Date: Thu, 10 Jun 2021 23:51:39 +0100 Subject: [PATCH 1/4] Implemented approve and revoke TokenProgram methods. --- src/Solnet.Programs/TokenProgram.cs | 860 ++++++++++-------- test/Solnet.Programs.Test/TokenProgramTest.cs | 337 ++++--- 2 files changed, 679 insertions(+), 518 deletions(-) diff --git a/src/Solnet.Programs/TokenProgram.cs b/src/Solnet.Programs/TokenProgram.cs index b1a3e39c..783292a7 100644 --- a/src/Solnet.Programs/TokenProgram.cs +++ b/src/Solnet.Programs/TokenProgram.cs @@ -1,373 +1,489 @@ -using NBitcoin.DataEncoders; -using Solnet.Rpc.Models; -using Solnet.Wallet; -using System; -using System.Collections.Generic; - -namespace Solnet.Programs -{ - public static class TokenProgram - { - /// - /// The base58 encoder instance. - /// - private static readonly Base58Encoder Encoder = new(); - - /// - /// The address of the Token Program. - /// - public const string ProgramId = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"; - - /// - /// The public key of the sysvar rent. - /// - public static string SysvarRentPublicKey = "SysvarRent111111111111111111111111111111111"; - - /// - /// Mint account ccount layout size. - /// - public const int MintAccountDataSize = 82; - - /// - /// Transfers tokens from one account to another either directly or via a delegate. - /// If this account is associated with the native mint then equal amounts of SOL and Tokens will be transferred to the destination account. - /// - /// The account to transfer tokens from. - /// The account to transfer tokens to. - /// The amount of tokens to transfer. - /// The account owner. - /// The transaction instruction. - public static TransactionInstruction Transfer( - string source, string destination, long amount, string owner) - { - return Transfer(Encoder.DecodeData(source), Encoder.DecodeData(destination), amount, Encoder.DecodeData(owner)); - } - - /// - /// Transfers tokens from one account to another either directly or via a delegate. - /// If this account is associated with the native mint then equal amounts of SOL and Tokens will be transferred to the destination account. - /// - /// The account to transfer tokens from. - /// The account to transfer tokens to. - /// The amount of tokens to transfer. - /// The account owner. - /// The transaction instruction. - public static TransactionInstruction Transfer( - byte[] source, byte[] destination, long amount, byte[] owner) - { - var keys = new List - { - new (source, false, true), - new (destination, false, true), - new (owner, true, false) - }; - - var transactionData = EncodeTransferData(amount); - - return new TransactionInstruction - { - ProgramId = Encoder.DecodeData(ProgramId), - Keys = keys, - Data = transactionData - }; - } - - /// - /// - /// Transfers tokens from one account to another either directly or via a delegate. - /// If this account is associated with the native mint then equal amounts of SOL and Tokens will be transferred to the destination account. - /// - /// - /// This instruction differs from Transfer in that the token mint and decimals value is checked by the caller. - /// This may be useful when creating transactions offline or within a hardware wallet. - /// - /// - /// The account to transfer tokens from. - /// The account to transfer tokens to. - /// The amount of tokens to transfer. - /// The token decimals. - /// The account owner. - /// The token mint. - /// The transaction instruction. - public static TransactionInstruction TransferChecked( - string source, string destination, long amount, byte decimals, string owner, string tokenMint) - { - return TransferChecked( - Encoder.DecodeData(source), - Encoder.DecodeData(destination), - amount, - decimals, - Encoder.DecodeData(owner), - Encoder.DecodeData(tokenMint)); - } - - - /// - /// - /// Transfers tokens from one account to another either directly or via a delegate. - /// If this account is associated with the native mint then equal amounts of SOL and Tokens will be transferred to the destination account. - /// - /// - /// This instruction differs from Transfer in that the token mint and decimals value is checked by the caller. - /// This may be useful when creating transactions offline or within a hardware wallet. - /// - /// - /// The account to transfer tokens from. - /// The account to transfer tokens to. - /// The amount of tokens to transfer. - /// The token decimals. - /// The account owner. - /// The token mint. - /// The transaction instruction. - public static TransactionInstruction TransferChecked( - byte[] source, byte[] destination, long amount, byte decimals, byte[] owner, byte[] tokenMint) - { - var keys = new List - { - new (source, false, true), - new (tokenMint, false, false), - new (destination, false, true), - new (owner, true, false) - }; - - var transactionData = EncodeTransferCheckedData(amount, decimals); - - return new TransactionInstruction - { - ProgramId = Encoder.DecodeData(ProgramId), - Keys = keys, - Data = transactionData - }; - } - - /// - /// Initializes a new account to hold tokens. - /// If this account is associated with the native mint then the token balance of the initialized account will be equal to the amount of SOL in the account. - /// If this account is associated with another mint, that mint must be initialized before this command can succeed. - /// - /// - /// The InitializeAccount instruction requires no signers and MUST be included within the same Transaction - /// as the system program's or - /// instruction that creates the account being initialized. - /// Otherwise another party can acquire ownership of the uninitialized account. - /// - /// - /// The account to initialize. - /// The token mint. - /// The account to set as owner of the initialized account. - /// The transaction instruction. - public static TransactionInstruction InitializeAccount(string account, string mint, string owner) - { - return InitializeAccount( - Encoder.DecodeData(account), - Encoder.DecodeData(mint), - Encoder.DecodeData(owner)); - } - - /// - /// Initializes a new account to hold tokens. - /// If this account is associated with the native mint then the token balance of the initialized account will be equal to the amount of SOL in the account. - /// If this account is associated with another mint, that mint must be initialized before this command can succeed. - /// - /// - /// The InitializeAccount instruction requires no signers and MUST be included within the same Transaction - /// as the system program's or - /// instruction that creates the account being initialized. - /// Otherwise another party can acquire ownership of the uninitialized account. - /// - /// - /// The account to initialize. - /// The token mint. - /// The account to set as owner of the initialized account. - /// The transaction instruction. - public static TransactionInstruction InitializeAccount(byte[] account, byte[] mint, byte[] owner) - { - var keys = new List - { - new (account, false, true), - new (mint, false, false), - new (owner, false, false), - new (Encoder.DecodeData(SysvarRentPublicKey), false, false) - }; - - return new() - { - ProgramId = Encoder.DecodeData(ProgramId), - Keys = keys, - Data = EncodeInitializeAccountData() - }; - } - - /// - /// Initialize a transaction to initialize a token mint account. - /// - /// The token mint. - /// The token decimals. - /// The token mint authority. - /// The token freeze authority. - /// The transaction instruction. - public static TransactionInstruction InitializeMint(string mint, int decimals, string mintAuthority, - string freezeAuthority = "") - { - return InitializeMint( - Encoder.DecodeData(mint), - decimals, - Encoder.DecodeData(mintAuthority), - !string.IsNullOrWhiteSpace(freezeAuthority) ? Encoder.DecodeData(mintAuthority) : null); - } - - /// - /// Initialize a transaction to initialize a token mint account. - /// - /// The token mint. - /// The token decimals. - /// The token mint authority. - /// The token freeze authority. - /// The transaction instruction. - public static TransactionInstruction InitializeMint(byte[] mint, int decimals, byte[] mintAuthority, byte[] freezeAuthority = null) - { - var keys = new List - { - new (mint, false, true), - new (Encoder.DecodeData(SysvarRentPublicKey), false, false) - }; - - var freezeAuthorityOpt = freezeAuthority != null ? 1 : 0; - freezeAuthority ??= new Account().PublicKey; - - var txData = EncodeInitializeMintData( - mintAuthority, - freezeAuthority, - decimals, - freezeAuthorityOpt); - - return new() - { - ProgramId = Encoder.DecodeData(ProgramId), - Keys = keys, - Data = txData - }; - } - - /// - /// Initializes a transaction to mint tokens to a destination account. - /// - /// The token mint. - /// The account to mint tokens to. - /// The amount of tokens. - /// The token mint authority. - /// The transaction instruction. - public static TransactionInstruction MintTo(string mint, string destination, long amount, string mintAuthority) - { - return MintTo( - Encoder.DecodeData(mint), - Encoder.DecodeData(destination), - amount, - Encoder.DecodeData(mintAuthority)); - } - - /// - /// Initializes a transaction to mint tokens to a destination account. - /// - /// The token mint. - /// The account to mint tokens to. - /// The amount of tokens. - /// The token mint authority. - /// The transaction instruction. - public static TransactionInstruction MintTo(byte[] mint, byte[] destination, long amount, byte[] mintAuthority) - { - var keys = new List - { - new (mint, false, true), - new (destination, false, true), - new (mintAuthority, true, false) - }; - - var data = EncodeMintToData(amount); - - return new() - { - ProgramId = Encoder.DecodeData(ProgramId), - Keys = keys, - Data = data - }; - } - - /// - /// Encode the transaction instruction data for the method. - /// - /// The byte array with the encoded data. - private static byte[] EncodeInitializeAccountData() => new[] { (byte)TokenProgramInstructions.InitializeAccount }; - - /// - /// Encode the transaction instruction data for the method. - /// - /// The mint authority for the token. - /// The freeze authority for the token. - /// The amount of decimals. - /// The freeze authority option for the token. - /// The freezeAuthorityOption parameter is related to the existence or not of a freeze authority. - /// The byte array with the encoded data. - private static byte[] EncodeInitializeMintData( - byte[] mintAuthority, byte[] freezeAuthority, int decimals, int freezeAuthorityOption) - { - var methodBuffer = new byte[67]; - - methodBuffer[0] = (byte)TokenProgramInstructions.InitializeMint; - methodBuffer[1] = (byte)decimals; - Array.Copy(mintAuthority, 0, methodBuffer, 2, 32); - methodBuffer[34] = (byte)freezeAuthorityOption; - Array.Copy(freezeAuthority, 0, methodBuffer, 35, 32); - - return methodBuffer; - } - - - /// - /// Encode the transaction instruction data for the method. - /// - /// The amount of tokens. - /// The byte array with the encoded data. - private static byte[] EncodeTransferData(long amount) - { - var methodBuffer = new byte[9]; - - methodBuffer[0] = (byte)TokenProgramInstructions.Transfer; - Utils.Int64ToByteArrayLe(amount, methodBuffer, 1); - - return methodBuffer; - } - - /// - /// Encode the transaction instruction data for the method. - /// - /// The amount of tokens. - /// The number of decimals of the token. - /// The byte array with the encoded data. - private static byte[] EncodeTransferCheckedData(long amount, byte decimals) - { - var methodBuffer = new byte[10]; - - methodBuffer[0] = (byte)TokenProgramInstructions.TransferChecked; - Utils.Int64ToByteArrayLe(amount, methodBuffer, 1); - methodBuffer[9] = decimals; - - return methodBuffer; - } - - /// - /// Encode the transaction instruction data for the method. - /// - /// The amount of tokens. - /// The byte array with the encoded data. - private static byte[] EncodeMintToData(long amount) - { - var methodBuffer = new byte[9]; - - methodBuffer[0] = (byte)TokenProgramInstructions.MintTo; - Utils.Int64ToByteArrayLe(amount, methodBuffer, 1); - - return methodBuffer; - } - } +using NBitcoin.DataEncoders; +using Solnet.Rpc.Models; +using Solnet.Wallet; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Solnet.Programs +{ + public static class TokenProgram + { + /// + /// The base58 encoder instance. + /// + private static readonly Base58Encoder Encoder = new(); + + /// + /// The address of the Token Program. + /// + public const string ProgramId = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"; + + /// + /// The public key of the sysvar rent. + /// + public static string SysvarRentPublicKey = "SysvarRent111111111111111111111111111111111"; + + /// + /// Mint account ccount layout size. + /// + public const int MintAccountDataSize = 82; + + /// + /// Initializes an instruction to transfer tokens from one account to another either directly or via a delegate. + /// If this account is associated with the native mint then equal amounts of SOL and Tokens will be transferred to the destination account. + /// + /// The account to transfer tokens from. + /// The account to transfer tokens to. + /// The amount of tokens to transfer. + /// The account owner. + /// The transaction instruction. + public static TransactionInstruction Transfer( + string source, string destination, long amount, string owner) + { + return Transfer(Encoder.DecodeData(source), Encoder.DecodeData(destination), amount, + Encoder.DecodeData(owner)); + } + + /// + /// Initializes an instruction to transfer tokens from one account to another either directly or via a delegate. + /// If this account is associated with the native mint then equal amounts of SOL and Tokens will be transferred to the destination account. + /// + /// The account to transfer tokens from. + /// The account to transfer tokens to. + /// The amount of tokens to transfer. + /// The account owner. + /// The transaction instruction. + public static TransactionInstruction Transfer( + byte[] source, byte[] destination, long amount, byte[] owner) + { + var keys = new List + { + new(source, false, true), new(destination, false, true), new(owner, true, false) + }; + + return new TransactionInstruction + { + ProgramId = Encoder.DecodeData(ProgramId), Keys = keys, Data = EncodeTransferData(amount) + }; + } + + /// + /// + /// Initializes an instruction to transfer tokens from one account to another either directly or via a delegate. + /// If this account is associated with the native mint then equal amounts of SOL and Tokens will be transferred to the destination account. + /// + /// + /// This instruction differs from Transfer in that the token mint and decimals value is checked by the caller. + /// This may be useful when creating transactions offline or within a hardware wallet. + /// + /// + /// The account to transfer tokens from. + /// The account to transfer tokens to. + /// The amount of tokens to transfer. + /// The token decimals. + /// The account owner. + /// The token mint. + /// The transaction instruction. + public static TransactionInstruction TransferChecked( + string source, string destination, long amount, byte decimals, string owner, string tokenMint) + { + return TransferChecked( + Encoder.DecodeData(source), + Encoder.DecodeData(destination), + amount, + decimals, + Encoder.DecodeData(owner), + Encoder.DecodeData(tokenMint)); + } + + + /// + /// + /// Initializes an instruction to transfer tokens from one account to another either directly or via a delegate. + /// If this account is associated with the native mint then equal amounts of SOL and Tokens will be transferred to the destination account. + /// + /// + /// This instruction differs from Transfer in that the token mint and decimals value is checked by the caller. + /// This may be useful when creating transactions offline or within a hardware wallet. + /// + /// + /// The account to transfer tokens from. + /// The account to transfer tokens to. + /// The amount of tokens to transfer. + /// The token decimals. + /// The account owner. + /// The token mint. + /// The transaction instruction. + public static TransactionInstruction TransferChecked( + byte[] source, byte[] destination, long amount, byte decimals, byte[] owner, byte[] tokenMint) + { + var keys = new List + { + new(source, false, true), + new(tokenMint, false, false), + new(destination, false, true), + new(owner, true, false) + }; + + return new TransactionInstruction + { + ProgramId = Encoder.DecodeData(ProgramId), + Keys = keys, + Data = EncodeTransferCheckedData(amount, decimals) + }; + } + + /// + /// Initializes an instruction to initialize a new account to hold tokens. + /// If this account is associated with the native mint then the token balance of the initialized account will be equal to the amount of SOL in the account. + /// If this account is associated with another mint, that mint must be initialized before this command can succeed. + /// + /// + /// The InitializeAccount instruction requires no signers and MUST be included within the same Transaction + /// as the system program's or + /// instruction that creates the account being initialized. + /// Otherwise another party can acquire ownership of the uninitialized account. + /// + /// + /// The account to initialize. + /// The token mint. + /// The account to set as owner of the initialized account. + /// The transaction instruction. + public static TransactionInstruction InitializeAccount(string account, string mint, string owner) + { + return InitializeAccount( + Encoder.DecodeData(account), + Encoder.DecodeData(mint), + Encoder.DecodeData(owner)); + } + + /// + /// Initializes an instruction to initialize a new account to hold tokens. + /// If this account is associated with the native mint then the token balance of the initialized account will be equal to the amount of SOL in the account. + /// If this account is associated with another mint, that mint must be initialized before this command can succeed. + /// + /// + /// The InitializeAccount instruction requires no signers and MUST be included within the same Transaction + /// as the system program's or + /// instruction that creates the account being initialized. + /// Otherwise another party can acquire ownership of the uninitialized account. + /// + /// + /// The account to initialize. + /// The token mint. + /// The account to set as owner of the initialized account. + /// The transaction instruction. + public static TransactionInstruction InitializeAccount(byte[] account, byte[] mint, byte[] owner) + { + var keys = new List + { + new(account, false, true), + new(mint, false, false), + new(owner, false, false), + new(Encoder.DecodeData(SysvarRentPublicKey), false, false) + }; + + return new() {ProgramId = Encoder.DecodeData(ProgramId), Keys = keys, Data = EncodeInitializeAccountData()}; + } + + /// + /// Initialize an instruction to initialize a token mint account. + /// + /// The token mint. + /// The token decimals. + /// The token mint authority. + /// The token freeze authority. + /// The transaction instruction. + public static TransactionInstruction InitializeMint(string mint, int decimals, string mintAuthority, + string freezeAuthority = "") + { + return InitializeMint( + Encoder.DecodeData(mint), + decimals, + Encoder.DecodeData(mintAuthority), + !string.IsNullOrWhiteSpace(freezeAuthority) ? Encoder.DecodeData(mintAuthority) : null); + } + + /// + /// Initialize an instruction to initialize a token mint account. + /// + /// The token mint. + /// The token decimals. + /// The token mint authority. + /// The token freeze authority. + /// The transaction instruction. + public static TransactionInstruction InitializeMint(byte[] mint, int decimals, byte[] mintAuthority, + byte[] freezeAuthority = null) + { + var keys = new List + { + new(mint, false, true), new(Encoder.DecodeData(SysvarRentPublicKey), false, false) + }; + + var freezeAuthorityOpt = freezeAuthority != null ? 1 : 0; + freezeAuthority ??= new Account().PublicKey; + + return new() + { + ProgramId = Encoder.DecodeData(ProgramId), + Keys = keys, + Data = EncodeInitializeMintData( + mintAuthority, + freezeAuthority, + decimals, + freezeAuthorityOpt) + }; + } + + /// + /// Initializes an instruction to mint tokens to a destination account. + /// + /// The token mint. + /// The account to mint tokens to. + /// The amount of tokens. + /// The token mint authority. + /// The transaction instruction. + public static TransactionInstruction MintTo(string mint, string destination, long amount, string mintAuthority) + { + return MintTo( + Encoder.DecodeData(mint), + Encoder.DecodeData(destination), + amount, + Encoder.DecodeData(mintAuthority)); + } + + /// + /// Initializes an instruction to mint tokens to a destination account. + /// + /// The token mint. + /// The account to mint tokens to. + /// The amount of tokens. + /// The token mint authority. + /// The transaction instruction. + public static TransactionInstruction MintTo(byte[] mint, byte[] destination, long amount, byte[] mintAuthority) + { + var keys = new List + { + new(mint, false, true), new(destination, false, true), new(mintAuthority, true, false) + }; + + return new() {ProgramId = Encoder.DecodeData(ProgramId), Keys = keys, Data = EncodeMintToData(amount)}; + } + + /// + /// Initializes an instruction to approve a transaction. + /// + /// The source account. + /// The delegate account authorized to perform a transfer from the source account. + /// The owner account of the source account. + /// The maximum amount of tokens the delegate may transfer. + /// Signing accounts if the `owner` is a multisig. + /// The transaction instruction. + public static TransactionInstruction Approve( + string sourceAccount, string delegateAccount, string ownerAccount, long amount, List signers = null) + { + if (signers == null) + { + return Approve( + Encoder.DecodeData(sourceAccount), + Encoder.DecodeData(delegateAccount), + Encoder.DecodeData(ownerAccount), + amount); + } + + List byteSigners = new(signers.Count); + byteSigners.AddRange(signers.Select(signer => Encoder.DecodeData(signer))); + return Approve( + Encoder.DecodeData(sourceAccount), + Encoder.DecodeData(delegateAccount), + Encoder.DecodeData(ownerAccount), + amount, byteSigners); + } + + /// + /// Initializes an instruction to approve a transaction. + /// + /// The source account. + /// The delegate account authorized to perform a transfer from the source account. + /// The owner account of the source account. + /// The maximum amount of tokens the delegate may transfer. + /// Signing accounts if the `owner` is a multisig. + /// The transaction instruction. + public static TransactionInstruction Approve( + byte[] sourceAccount, byte[] delegateAccount, byte[] ownerAccount, long amount, List signers = null) + { + var keys = new List {new(sourceAccount, false, true), new(delegateAccount, false, false)}; + + keys = AddSigners(keys, ownerAccount, signers); + + return new() {ProgramId = Encoder.DecodeData(ProgramId), Keys = keys, Data = EncodeApproveData(amount)}; + } + + /// + /// Initializes an instruction to revoke a transaction. + /// + /// The delegate account authorized to perform a transfer from the source account. + /// The owner account of the source account. + /// Signing accounts if the `owner` is a multisig. + /// The transaction instruction. + public static TransactionInstruction Revoke(string delegateAccount, string ownerAccount, List signers = null) + { + if (signers == null) + { + return Revoke( + Encoder.DecodeData(delegateAccount), + Encoder.DecodeData(ownerAccount)); + } + List byteSigners = new(signers.Count); + byteSigners.AddRange(signers.Select(signer => Encoder.DecodeData(signer))); + return Revoke( + Encoder.DecodeData(delegateAccount), + Encoder.DecodeData(ownerAccount), + byteSigners); + } + + /// + /// Initializes an instruction to revoke a transaction. + /// + /// The delegate account authorized to perform a transfer from the source account. + /// The owner account of the source account. + /// Signing accounts if the `owner` is a multisig. + /// The transaction instruction. + public static TransactionInstruction Revoke(byte[] delegateAccount, byte[] ownerAccount, List signers = null) + { + var keys = new List {new(delegateAccount, false, false),}; + keys = AddSigners(keys, ownerAccount, signers); + + return new TransactionInstruction + { + ProgramId = Encoder.DecodeData(ProgramId), Keys = keys, Data = EncodeRevokeData() + }; + } + + /// + /// Adds the list of signers to the list of keys. + /// + /// The instruction's list of keys. + /// The owner account. + /// The list of signers. + /// The list of keys with the added signers. + private static List AddSigners(List keys, byte[] owner = null, + IEnumerable signers = null) + { + if (signers != null) + { + keys.Add(new AccountMeta(owner, false, false)); + keys.AddRange(signers.Select(signer => new AccountMeta(signer, true, false))); + } + else + { + keys.Add(new AccountMeta(owner, true, false)); + } + + return keys; + } + + /// + /// Encode the transaction instruction data for the method. + /// + /// The byte array with the encoded data. + private static byte[] EncodeRevokeData() + { + var methodBuffer = new byte[1]; + methodBuffer[0] = (byte)TokenProgramInstructions.Revoke; + return methodBuffer; + } + + /// + /// Encode the transaction instruction data for the method. + /// + /// The amount of tokens to approve the transfer of. + /// The byte array with the encoded data. + private static byte[] EncodeApproveData(long amount) + { + var methodBuffer = new byte[9]; + + methodBuffer[0] = (byte)TokenProgramInstructions.Approve; + Utils.Int64ToByteArrayLe(amount, methodBuffer, 1); + return methodBuffer; + } + + /// + /// Encode the transaction instruction data for the method. + /// + /// The byte array with the encoded data. + private static byte[] EncodeInitializeAccountData() => new[] {(byte)TokenProgramInstructions.InitializeAccount}; + + /// + /// Encode the transaction instruction data for the method. + /// + /// The mint authority for the token. + /// The freeze authority for the token. + /// The amount of decimals. + /// The freeze authority option for the token. + /// The freezeAuthorityOption parameter is related to the existence or not of a freeze authority. + /// The byte array with the encoded data. + private static byte[] EncodeInitializeMintData( + byte[] mintAuthority, byte[] freezeAuthority, int decimals, int freezeAuthorityOption) + { + var methodBuffer = new byte[67]; + + methodBuffer[0] = (byte)TokenProgramInstructions.InitializeMint; + methodBuffer[1] = (byte)decimals; + Array.Copy(mintAuthority, 0, methodBuffer, 2, 32); + methodBuffer[34] = (byte)freezeAuthorityOption; + Array.Copy(freezeAuthority, 0, methodBuffer, 35, 32); + + return methodBuffer; + } + + + /// + /// Encode the transaction instruction data for the method. + /// + /// The amount of tokens. + /// The byte array with the encoded data. + private static byte[] EncodeTransferData(long amount) + { + var methodBuffer = new byte[9]; + + methodBuffer[0] = (byte)TokenProgramInstructions.Transfer; + Utils.Int64ToByteArrayLe(amount, methodBuffer, 1); + + return methodBuffer; + } + + /// + /// Encode the transaction instruction data for the method. + /// + /// The amount of tokens. + /// The number of decimals of the token. + /// The byte array with the encoded data. + private static byte[] EncodeTransferCheckedData(long amount, byte decimals) + { + var methodBuffer = new byte[10]; + + methodBuffer[0] = (byte)TokenProgramInstructions.TransferChecked; + Utils.Int64ToByteArrayLe(amount, methodBuffer, 1); + methodBuffer[9] = decimals; + + return methodBuffer; + } + + /// + /// Encode the transaction instruction data for the method. + /// + /// The amount of tokens. + /// The byte array with the encoded data. + private static byte[] EncodeMintToData(long amount) + { + var methodBuffer = new byte[9]; + + methodBuffer[0] = (byte)TokenProgramInstructions.MintTo; + Utils.Int64ToByteArrayLe(amount, methodBuffer, 1); + + return methodBuffer; + } + } } \ No newline at end of file diff --git a/test/Solnet.Programs.Test/TokenProgramTest.cs b/test/Solnet.Programs.Test/TokenProgramTest.cs index 94888f43..a5b9a5ea 100644 --- a/test/Solnet.Programs.Test/TokenProgramTest.cs +++ b/test/Solnet.Programs.Test/TokenProgramTest.cs @@ -1,147 +1,192 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace Solnet.Programs.Test -{ - [TestClass] - public class TokenProgramTest - { - private const string MnemonicWords = - "route clerk disease box emerge airport loud waste attitude film army tray " + - "forward deal onion eight catalog surface unit card window walnut wealth medal"; - - private static readonly byte[] TokenProgramIdBytes = - { - 6, 221, 246, 225, 215, 101, 161, 147, 217, 203, - 225, 70, 206, 235, 121, 172, 28, 180, 133, 237, - 95, 91, 55, 145, 58, 140, 245, 133, 126, 255, 0, 169 - }; - - private static readonly byte[] ExpectedTransferData = - { - 3, 168, 97, 0, 0, 0, 0, 0, 0 - }; - - private static readonly byte[] ExpectedTransferCheckedData = - { - 12, 168, 97, 0, 0, 0, 0, 0, 0, 2 - }; - - private static readonly byte[] ExpectedInitializeMintData = - { - 0, 2, 71, 105, 171, 151, 32, 75, 168, 63, 176, 202, 238, 23, 247, 134, 143, 30, 7, 78, 82, 21, 129, - 160, 216, 157, 148, 55, 157, 170, 101, 183, 23, 178, 1, 71, 105, 171, 151, 32, 75, 168, 63, 176, - 202, 238, 23, 247, 134, 143, 30, 7, 78, 82, 21, 129, 160, 216, 157, 148, 55, 157, 170, 101, 183, 23, 178 - }; - - private static readonly byte[] ExpectedMintToData = - { - 7, 168, 97, 0, 0, 0, 0, 0, 0 - }; - - private static readonly byte[] ExpectedInitializeAccountData = { 1 }; - - [TestMethod] - public void TestTransfer() - { - var wallet = new Wallet.Wallet(MnemonicWords); - - var ownerAccount = wallet.GetAccount(10); - var initialAccount = wallet.GetAccount(24); - var newAccount = wallet.GetAccount(26); - - var txInstruction = TokenProgram.Transfer( - initialAccount.GetPublicKey, - newAccount.GetPublicKey, - 25000, - ownerAccount.GetPublicKey); - - Assert.AreEqual(3, txInstruction.Keys.Count); - CollectionAssert.AreEqual(TokenProgramIdBytes, txInstruction.ProgramId); - CollectionAssert.AreEqual(ExpectedTransferData, txInstruction.Data); - } - - [TestMethod] - public void TestTransferChecked() - { - var wallet = new Wallet.Wallet(MnemonicWords); - - var mintAccount = wallet.GetAccount(21); - var ownerAccount = wallet.GetAccount(10); - var initialAccount = wallet.GetAccount(26); - var newAccount = wallet.GetAccount(27); - - var txInstruction = TokenProgram.TransferChecked( - initialAccount.GetPublicKey, - newAccount.GetPublicKey, - 25000, - 2, - ownerAccount.GetPublicKey, - mintAccount.GetPublicKey); - - - Assert.AreEqual(4, txInstruction.Keys.Count); - CollectionAssert.AreEqual(TokenProgramIdBytes, txInstruction.ProgramId); - CollectionAssert.AreEqual(ExpectedTransferCheckedData, txInstruction.Data); - } - - [TestMethod] - public void TestInitializeAccount() - { - var wallet = new Wallet.Wallet(MnemonicWords); - - var mintAccount = wallet.GetAccount(21); - var ownerAccount = wallet.GetAccount(10); - var initialAccount = wallet.GetAccount(22); - - var txInstruction = - TokenProgram.InitializeAccount( - initialAccount.GetPublicKey, - mintAccount.GetPublicKey, - ownerAccount.GetPublicKey); - - Assert.AreEqual(4, txInstruction.Keys.Count); - CollectionAssert.AreEqual(TokenProgramIdBytes, txInstruction.ProgramId); - CollectionAssert.AreEqual(ExpectedInitializeAccountData, txInstruction.Data); - } - - [TestMethod] - public void TestInitializeMint() - { - var wallet = new Wallet.Wallet(MnemonicWords); - - var mintAccount = wallet.GetAccount(21); - var ownerAccount = wallet.GetAccount(10); - - var txInstruction = TokenProgram.InitializeMint( - mintAccount.GetPublicKey, - 2, - ownerAccount.GetPublicKey, - ownerAccount.GetPublicKey); - - Assert.AreEqual(2, txInstruction.Keys.Count); - CollectionAssert.AreEqual(TokenProgramIdBytes, txInstruction.ProgramId); - CollectionAssert.AreEqual(ExpectedInitializeMintData, txInstruction.Data); - } - - [TestMethod] - public void TestMintTo() - { - var wallet = new Wallet.Wallet(MnemonicWords); - - var mintAccount = wallet.GetAccount(21); - var ownerAccount = wallet.GetAccount(10); - var initialAccount = wallet.GetAccount(22); - - var txInstruction = - TokenProgram.MintTo( - mintAccount.GetPublicKey, - initialAccount.GetPublicKey, - 25000, - ownerAccount.GetPublicKey); - - Assert.AreEqual(3, txInstruction.Keys.Count); - CollectionAssert.AreEqual(TokenProgramIdBytes, txInstruction.ProgramId); - CollectionAssert.AreEqual(ExpectedMintToData, txInstruction.Data); - } - } +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Collections.Generic; + +namespace Solnet.Programs.Test +{ + [TestClass] + public class TokenProgramTest + { + private const string MnemonicWords = + "route clerk disease box emerge airport loud waste attitude film army tray " + + "forward deal onion eight catalog surface unit card window walnut wealth medal"; + + private static readonly byte[] TokenProgramIdBytes = + { + 6, 221, 246, 225, 215, 101, 161, 147, 217, 203, + 225, 70, 206, 235, 121, 172, 28, 180, 133, 237, + 95, 91, 55, 145, 58, 140, 245, 133, 126, 255, 0, 169 + }; + + private static readonly byte[] ExpectedTransferData = + { + 3, 168, 97, 0, 0, 0, 0, 0, 0 + }; + + private static readonly byte[] ExpectedTransferCheckedData = + { + 12, 168, 97, 0, 0, 0, 0, 0, 0, 2 + }; + + private static readonly byte[] ExpectedInitializeMintData = + { + 0, 2, 71, 105, 171, 151, 32, 75, 168, 63, 176, 202, 238, 23, 247, 134, 143, 30, 7, 78, 82, 21, 129, + 160, 216, 157, 148, 55, 157, 170, 101, 183, 23, 178, 1, 71, 105, 171, 151, 32, 75, 168, 63, 176, + 202, 238, 23, 247, 134, 143, 30, 7, 78, 82, 21, 129, 160, 216, 157, 148, 55, 157, 170, 101, 183, 23, 178 + }; + + private static readonly byte[] ExpectedMintToData = + { + 7, 168, 97, 0, 0, 0, 0, 0, 0 + }; + + private static readonly byte[] ExpectedInitializeAccountData = { 1 }; + + private static readonly byte[] ExpectedApproveData = { 4, 168, 97, 0, 0, 0, 0, 0, 0 }; + + private static readonly byte[] ExpectedRevokeData = { 5 }; + + [TestMethod] + public void TestTransfer() + { + var wallet = new Wallet.Wallet(MnemonicWords); + + var ownerAccount = wallet.GetAccount(10); + var initialAccount = wallet.GetAccount(24); + var newAccount = wallet.GetAccount(26); + + var txInstruction = TokenProgram.Transfer( + initialAccount.GetPublicKey, + newAccount.GetPublicKey, + 25000, + ownerAccount.GetPublicKey); + + Assert.AreEqual(3, txInstruction.Keys.Count); + CollectionAssert.AreEqual(TokenProgramIdBytes, txInstruction.ProgramId); + CollectionAssert.AreEqual(ExpectedTransferData, txInstruction.Data); + } + + [TestMethod] + public void TestTransferChecked() + { + var wallet = new Wallet.Wallet(MnemonicWords); + + var mintAccount = wallet.GetAccount(21); + var ownerAccount = wallet.GetAccount(10); + var initialAccount = wallet.GetAccount(26); + var newAccount = wallet.GetAccount(27); + + var txInstruction = TokenProgram.TransferChecked( + initialAccount.GetPublicKey, + newAccount.GetPublicKey, + 25000, + 2, + ownerAccount.GetPublicKey, + mintAccount.GetPublicKey); + + + Assert.AreEqual(4, txInstruction.Keys.Count); + CollectionAssert.AreEqual(TokenProgramIdBytes, txInstruction.ProgramId); + CollectionAssert.AreEqual(ExpectedTransferCheckedData, txInstruction.Data); + } + + [TestMethod] + public void TestInitializeAccount() + { + var wallet = new Wallet.Wallet(MnemonicWords); + + var mintAccount = wallet.GetAccount(21); + var ownerAccount = wallet.GetAccount(10); + var initialAccount = wallet.GetAccount(22); + + var txInstruction = + TokenProgram.InitializeAccount( + initialAccount.GetPublicKey, + mintAccount.GetPublicKey, + ownerAccount.GetPublicKey); + + Assert.AreEqual(4, txInstruction.Keys.Count); + CollectionAssert.AreEqual(TokenProgramIdBytes, txInstruction.ProgramId); + CollectionAssert.AreEqual(ExpectedInitializeAccountData, txInstruction.Data); + } + + [TestMethod] + public void TestInitializeMint() + { + var wallet = new Wallet.Wallet(MnemonicWords); + + var mintAccount = wallet.GetAccount(21); + var ownerAccount = wallet.GetAccount(10); + + var txInstruction = TokenProgram.InitializeMint( + mintAccount.GetPublicKey, + 2, + ownerAccount.GetPublicKey, + ownerAccount.GetPublicKey); + + Assert.AreEqual(2, txInstruction.Keys.Count); + CollectionAssert.AreEqual(TokenProgramIdBytes, txInstruction.ProgramId); + CollectionAssert.AreEqual(ExpectedInitializeMintData, txInstruction.Data); + } + + [TestMethod] + public void TestMintTo() + { + var wallet = new Wallet.Wallet(MnemonicWords); + + var mintAccount = wallet.GetAccount(21); + var ownerAccount = wallet.GetAccount(10); + var initialAccount = wallet.GetAccount(22); + + var txInstruction = + TokenProgram.MintTo( + mintAccount.GetPublicKey, + initialAccount.GetPublicKey, + 25000, + ownerAccount.GetPublicKey); + + Assert.AreEqual(3, txInstruction.Keys.Count); + CollectionAssert.AreEqual(TokenProgramIdBytes, txInstruction.ProgramId); + CollectionAssert.AreEqual(ExpectedMintToData, txInstruction.Data); + } + + [TestMethod] + public void TestApprove() + { + var wallet = new Wallet.Wallet(MnemonicWords); + + var sourceAccount = wallet.GetAccount(69); + var delegateAccount = wallet.GetAccount(420); + var ownerAccount = wallet.GetAccount(1); + + var txInstruction = + TokenProgram.Approve( + sourceAccount.GetPublicKey, + delegateAccount.GetPublicKey, + ownerAccount.GetPublicKey, + 25000, null); + + Assert.AreEqual(3, txInstruction.Keys.Count); + CollectionAssert.AreEqual(TokenProgramIdBytes, txInstruction.ProgramId); + CollectionAssert.AreEqual(ExpectedApproveData, txInstruction.Data); + } + + [TestMethod] + public void TestRevoke() + { + var wallet = new Wallet.Wallet(MnemonicWords); + + var sourceAccount = wallet.GetAccount(69); + var delegateAccount = wallet.GetAccount(420); + var ownerAccount = wallet.GetAccount(1); + + var txInstruction = + TokenProgram.Revoke( + delegateAccount.GetPublicKey, + ownerAccount.GetPublicKey, null); + + Assert.AreEqual(2, txInstruction.Keys.Count); + CollectionAssert.AreEqual(TokenProgramIdBytes, txInstruction.ProgramId); + CollectionAssert.AreEqual(ExpectedRevokeData, txInstruction.Data); + } + } } \ No newline at end of file From 7c04ce8cbe7f2a7ba4e1d9698cf38cff38f61dd9 Mon Sep 17 00:00:00 2001 From: murlux Date: Fri, 11 Jun 2021 19:58:19 +0100 Subject: [PATCH 2/4] Running dotnet format --- .config/dotnet-tools.json | 6 ++++ src/Solnet.Programs/TokenProgram.cs | 29 ++++++++++++------- test/Solnet.Programs.Test/TokenProgramTest.cs | 6 ++-- 3 files changed, 27 insertions(+), 14 deletions(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 6cf141eb..9895427e 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -7,6 +7,12 @@ "commands": [ "dotnet-cake" ] + }, + "dotnet-format": { + "version": "5.1.225507", + "commands": [ + "dotnet-format" + ] } } } \ No newline at end of file diff --git a/src/Solnet.Programs/TokenProgram.cs b/src/Solnet.Programs/TokenProgram.cs index 783292a7..c43cf359 100644 --- a/src/Solnet.Programs/TokenProgram.cs +++ b/src/Solnet.Programs/TokenProgram.cs @@ -64,7 +64,9 @@ public static TransactionInstruction Transfer( return new TransactionInstruction { - ProgramId = Encoder.DecodeData(ProgramId), Keys = keys, Data = EncodeTransferData(amount) + ProgramId = Encoder.DecodeData(ProgramId), + Keys = keys, + Data = EncodeTransferData(amount) }; } @@ -184,7 +186,7 @@ public static TransactionInstruction InitializeAccount(byte[] account, byte[] mi new(Encoder.DecodeData(SysvarRentPublicKey), false, false) }; - return new() {ProgramId = Encoder.DecodeData(ProgramId), Keys = keys, Data = EncodeInitializeAccountData()}; + return new() { ProgramId = Encoder.DecodeData(ProgramId), Keys = keys, Data = EncodeInitializeAccountData() }; } /// @@ -268,7 +270,7 @@ public static TransactionInstruction MintTo(byte[] mint, byte[] destination, lon new(mint, false, true), new(destination, false, true), new(mintAuthority, true, false) }; - return new() {ProgramId = Encoder.DecodeData(ProgramId), Keys = keys, Data = EncodeMintToData(amount)}; + return new() { ProgramId = Encoder.DecodeData(ProgramId), Keys = keys, Data = EncodeMintToData(amount) }; } /// @@ -313,11 +315,11 @@ public static TransactionInstruction Approve( public static TransactionInstruction Approve( byte[] sourceAccount, byte[] delegateAccount, byte[] ownerAccount, long amount, List signers = null) { - var keys = new List {new(sourceAccount, false, true), new(delegateAccount, false, false)}; + var keys = new List { new(sourceAccount, false, true), new(delegateAccount, false, false) }; keys = AddSigners(keys, ownerAccount, signers); - return new() {ProgramId = Encoder.DecodeData(ProgramId), Keys = keys, Data = EncodeApproveData(amount)}; + return new() { ProgramId = Encoder.DecodeData(ProgramId), Keys = keys, Data = EncodeApproveData(amount) }; } /// @@ -327,7 +329,8 @@ public static TransactionInstruction Approve( /// The owner account of the source account. /// Signing accounts if the `owner` is a multisig. /// The transaction instruction. - public static TransactionInstruction Revoke(string delegateAccount, string ownerAccount, List signers = null) + public static TransactionInstruction Revoke(string delegateAccount, string ownerAccount, + List signers = null) { if (signers == null) { @@ -335,6 +338,7 @@ public static TransactionInstruction Revoke(string delegateAccount, string owner Encoder.DecodeData(delegateAccount), Encoder.DecodeData(ownerAccount)); } + List byteSigners = new(signers.Count); byteSigners.AddRange(signers.Select(signer => Encoder.DecodeData(signer))); return Revoke( @@ -350,17 +354,20 @@ public static TransactionInstruction Revoke(string delegateAccount, string owner /// The owner account of the source account. /// Signing accounts if the `owner` is a multisig. /// The transaction instruction. - public static TransactionInstruction Revoke(byte[] delegateAccount, byte[] ownerAccount, List signers = null) + public static TransactionInstruction Revoke(byte[] delegateAccount, byte[] ownerAccount, + List signers = null) { - var keys = new List {new(delegateAccount, false, false),}; + var keys = new List { new(delegateAccount, false, false), }; keys = AddSigners(keys, ownerAccount, signers); return new TransactionInstruction { - ProgramId = Encoder.DecodeData(ProgramId), Keys = keys, Data = EncodeRevokeData() + ProgramId = Encoder.DecodeData(ProgramId), + Keys = keys, + Data = EncodeRevokeData() }; } - + /// /// Adds the list of signers to the list of keys. /// @@ -413,7 +420,7 @@ private static byte[] EncodeApproveData(long amount) /// Encode the transaction instruction data for the method. /// /// The byte array with the encoded data. - private static byte[] EncodeInitializeAccountData() => new[] {(byte)TokenProgramInstructions.InitializeAccount}; + private static byte[] EncodeInitializeAccountData() => new[] { (byte)TokenProgramInstructions.InitializeAccount }; /// /// Encode the transaction instruction data for the method. diff --git a/test/Solnet.Programs.Test/TokenProgramTest.cs b/test/Solnet.Programs.Test/TokenProgramTest.cs index a5b9a5ea..a98aa811 100644 --- a/test/Solnet.Programs.Test/TokenProgramTest.cs +++ b/test/Solnet.Programs.Test/TokenProgramTest.cs @@ -42,7 +42,7 @@ public class TokenProgramTest private static readonly byte[] ExpectedInitializeAccountData = { 1 }; private static readonly byte[] ExpectedApproveData = { 4, 168, 97, 0, 0, 0, 0, 0, 0 }; - + private static readonly byte[] ExpectedRevokeData = { 5 }; [TestMethod] @@ -148,7 +148,7 @@ public void TestMintTo() CollectionAssert.AreEqual(TokenProgramIdBytes, txInstruction.ProgramId); CollectionAssert.AreEqual(ExpectedMintToData, txInstruction.Data); } - + [TestMethod] public void TestApprove() { @@ -169,7 +169,7 @@ public void TestApprove() CollectionAssert.AreEqual(TokenProgramIdBytes, txInstruction.ProgramId); CollectionAssert.AreEqual(ExpectedApproveData, txInstruction.Data); } - + [TestMethod] public void TestRevoke() { From 176c7acc2094cf2cbc8a848ed44f90f331208388 Mon Sep 17 00:00:00 2001 From: murlux Date: Fri, 11 Jun 2021 20:03:30 +0100 Subject: [PATCH 3/4] Fixing cake build order. --- build.cake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.cake b/build.cake index c16993f6..65758080 100644 --- a/build.cake +++ b/build.cake @@ -104,7 +104,7 @@ Task("Publish") }); Task("Pack") - .IsDependentOn("Test") + .IsDependentOn("Publish") .Does(() => { var settings = new DotNetCorePackSettings From 2dde15bf19e3f7b9f7bcc6962fbe0aee0528757d Mon Sep 17 00:00:00 2001 From: murlux Date: Fri, 11 Jun 2021 20:07:28 +0100 Subject: [PATCH 4/4] Running dotnet format --- .../SolanaKeygenKeyGeneration.cs | 66 +- src/Solnet.Examples/SolletKeyGeneration.cs | 88 +- src/Solnet.Examples/SolletKeygenKeystore.cs | 166 +- src/Solnet.Examples/SolnetRpcTester.cs | 222 +- .../SolnetStreamingRpcTester.cs | 90 +- .../TransactionBuilderExample.cs | 438 +-- .../Crypto/IRandomBytesGenerator.cs | 14 +- src/Solnet.KeyStore/Crypto/KeyStoreCrypto.cs | 224 +- .../Crypto/RandomBytesGenerator.cs | 50 +- src/Solnet.KeyStore/Crypto/Scrypt.cs | 634 ++-- .../Exceptions/DecryptionException.cs | 20 +- .../Exceptions/InvalidKdfException.cs | 20 +- src/Solnet.KeyStore/KeyStoreKdfChecker.cs | 114 +- src/Solnet.KeyStore/Model/CipherParams.cs | 38 +- src/Solnet.KeyStore/Model/CryptoInfo.cs | 102 +- src/Solnet.KeyStore/Model/KdfParams.cs | 28 +- src/Solnet.KeyStore/Model/KdfType.cs | 14 +- src/Solnet.KeyStore/Model/KeyStore.cs | 36 +- src/Solnet.KeyStore/Model/Pbkdf2Params.cs | 28 +- src/Solnet.KeyStore/Model/ScryptParams.cs | 30 +- src/Solnet.KeyStore/SecretKeyStoreService.cs | 160 +- .../JsonKeyStorePbkdf2Serializer.cs | 32 +- .../JsonKeyStoreScryptSerializer.cs | 32 +- .../Services/ISecretKeyStore.cs | 102 +- .../Services/KeyStorePbkdf2Service.cs | 124 +- .../Services/KeyStoreScryptService.cs | 134 +- .../Services/KeyStoreServiceBase.cs | 212 +- src/Solnet.KeyStore/SolanaKeyStore.cs | 116 +- src/Solnet.KeyStore/Utils.cs | 260 +- src/Solnet.Programs/MemoProgram.cs | 98 +- src/Solnet.Programs/SystemProgram.cs | 268 +- .../TokenProgramInstructions.cs | 182 +- src/Solnet.Programs/Utils.cs | 78 +- src/Solnet.Rpc/Builders/MessageBuilder.cs | 518 +-- src/Solnet.Rpc/Builders/TransactionBuilder.cs | 256 +- src/Solnet.Rpc/ClientFactory.cs | 252 +- src/Solnet.Rpc/Cluster.cs | 16 +- src/Solnet.Rpc/Core/Http/JsonRpcClient.cs | 286 +- src/Solnet.Rpc/Core/Http/RequestResult.cs | 74 +- src/Solnet.Rpc/Core/IdGenerator.cs | 50 +- src/Solnet.Rpc/Core/Sockets/IWebSocket.cs | 32 +- .../Core/Sockets/StreamingRpcClient.cs | 236 +- .../Core/Sockets/SubscriptionChannel.cs | 22 +- .../Core/Sockets/SubscriptionEvent.cs | 74 +- .../Core/Sockets/SubscriptionState.cs | 236 +- .../Core/Sockets/SubscriptionStatus.cs | 54 +- .../Core/Sockets/WebSocketWrapper.cs | 104 +- src/Solnet.Rpc/IRpcClient.cs | 1422 ++++---- src/Solnet.Rpc/IStreamingRpcClient.cs | 294 +- src/Solnet.Rpc/Messages/JsonRpcBase.cs | 16 +- src/Solnet.Rpc/Messages/JsonRpcRequest.cs | 40 +- src/Solnet.Rpc/Messages/JsonRpcResponse.cs | 70 +- .../Messages/JsonRpcStreamResponse.cs | 16 +- src/Solnet.Rpc/Models/AccountData.cs | 286 +- src/Solnet.Rpc/Models/AccountInfo.cs | 134 +- src/Solnet.Rpc/Models/AccountKeysList.cs | 146 +- src/Solnet.Rpc/Models/AccountMeta.cs | 150 +- src/Solnet.Rpc/Models/Block.cs | 726 ++-- src/Solnet.Rpc/Models/BlockProductionInfo.cs | 74 +- src/Solnet.Rpc/Models/ClusterNode.cs | 84 +- src/Solnet.Rpc/Models/Epoch.cs | 126 +- src/Solnet.Rpc/Models/ErrorResult.cs | 18 +- src/Solnet.Rpc/Models/Fees.cs | 138 +- src/Solnet.Rpc/Models/Identity.cs | 24 +- src/Solnet.Rpc/Models/Inflation.cs | 170 +- src/Solnet.Rpc/Models/InstructionError.cs | 132 +- src/Solnet.Rpc/Models/Logs.cs | 114 +- src/Solnet.Rpc/Models/Performance.cs | 54 +- src/Solnet.Rpc/Models/Signature.cs | 90 +- src/Solnet.Rpc/Models/SlotInfo.cs | 48 +- src/Solnet.Rpc/Models/StakeActivation.cs | 44 +- src/Solnet.Rpc/Models/Supply.cs | 60 +- src/Solnet.Rpc/Models/TokenAccount.cs | 44 +- src/Solnet.Rpc/Models/TransactionError.cs | 66 +- .../Models/TransactionErrorJsonConverter.cs | 244 +- .../Models/TransactionInstruction.cs | 48 +- src/Solnet.Rpc/Models/Version.cs | 42 +- src/Solnet.Rpc/Models/VoteAccount.cs | 140 +- src/Solnet.Rpc/SolanaRpcClient.cs | 2032 +++++------ src/Solnet.Rpc/SolanaStreamingRpcClient.cs | 914 ++--- src/Solnet.Rpc/Types/BinaryEncoding.cs | 40 +- src/Solnet.Rpc/Types/Commitment.cs | 16 +- src/Solnet.Rpc/Types/LogsSubscriptionType.cs | 14 +- src/Solnet.Rpc/Types/TransactionDetails.cs | 40 +- src/Solnet.Rpc/Utilities/AddressExtensions.cs | 166 +- src/Solnet.Rpc/Utilities/Ed25519Extensions.cs | 192 +- .../Utilities/ShortVectorEncoding.cs | 66 +- src/Solnet.Wallet/Account.cs | 230 +- src/Solnet.Wallet/Ed25519Bip32.cs | 266 +- src/Solnet.Wallet/Ed25519Extensions.cs | 28 +- src/Solnet.Wallet/SeedMode.cs | 46 +- .../Utilities/BigEndianBuffer.cs | 82 +- src/Solnet.Wallet/Utilities/Utils.cs | 78 +- src/Solnet.Wallet/Wallet.cs | 444 +-- .../KeyStoreKdfCheckerTest.cs | 60 +- .../SecretKeyStoreServiceTest.cs | 278 +- .../SolanaKeygenKeyStoreTest.cs | 180 +- .../Solnet.Programs.Test/SystemProgramTest.cs | 148 +- test/Solnet.Rpc.Test/ClientFactoryTest.cs | 86 +- .../SolanaRpcClientAccountTests.cs | 304 +- .../SolanaRpcClientBlockTests.cs | 1016 +++--- test/Solnet.Rpc.Test/SolanaRpcClientTest.cs | 3142 ++++++++--------- .../SolanaStreamingClientTest.cs | 1022 +++--- .../Solnet.Rpc.Test/TransactionBuilderTest.cs | 230 +- test/Solnet.Rpc.Test/UtilitiesTest.cs | 126 +- test/Solnet.Wallet.Test/AccountTest.cs | 270 +- test/Solnet.Wallet.Test/Ed25519Bip32Test.cs | 72 +- test/Solnet.Wallet.Test/WalletTest.cs | 506 +-- 108 files changed, 11792 insertions(+), 11792 deletions(-) diff --git a/src/Solnet.Examples/SolanaKeygenKeyGeneration.cs b/src/Solnet.Examples/SolanaKeygenKeyGeneration.cs index 081769c8..f9ead992 100644 --- a/src/Solnet.Examples/SolanaKeygenKeyGeneration.cs +++ b/src/Solnet.Examples/SolanaKeygenKeyGeneration.cs @@ -1,34 +1,34 @@ -using NBitcoin; -using Solnet.Wallet; -using System; - -namespace Solnet.Examples -{ - public class SolanaKeygenWallet - { - static void Example() - { - const string expectedSolKeygenPublicKey = "AZzmpdbZWARkPzL8GKRHjjwY74st4URgk9v7QBubeWba"; - const string expectedSolKeygenPrivateKey = "2RitwnKZwoigHk9S3ftvFQhoTy5QQKAipNjZHDgCet8hyciUbJSuhMWDKRL8JKE784pK8jJPFaNerFsS6KXhY9K6"; - - // mnemonic and passphrase to derive seed - var passphrase = "thisiseightbytesithink"; - var mnemonic = new Mnemonic("route clerk disease box emerge airport loud waste attitude film army tray forward deal onion eight catalog surface unit card window walnut wealth medal", Wordlist.English); - - var solKeygenWallet = new Wallet.Wallet(mnemonic, passphrase, SeedMode.Bip39); - - Console.WriteLine($"SOLLET publicKey>b58 {solKeygenWallet.Account.GetPublicKey}"); - Console.WriteLine($"SOLLET privateKey>b58 {solKeygenWallet.Account.GetPrivateKey}"); - - if (solKeygenWallet.Account.GetPublicKey != expectedSolKeygenPublicKey || solKeygenWallet.Account.GetPrivateKey != expectedSolKeygenPrivateKey) - { - Console.WriteLine("NOT GOOD FOR THE SOL"); - } - else - { - Console.WriteLine("GOOD FOR THE SOL"); - } - - } - } +using NBitcoin; +using Solnet.Wallet; +using System; + +namespace Solnet.Examples +{ + public class SolanaKeygenWallet + { + static void Example() + { + const string expectedSolKeygenPublicKey = "AZzmpdbZWARkPzL8GKRHjjwY74st4URgk9v7QBubeWba"; + const string expectedSolKeygenPrivateKey = "2RitwnKZwoigHk9S3ftvFQhoTy5QQKAipNjZHDgCet8hyciUbJSuhMWDKRL8JKE784pK8jJPFaNerFsS6KXhY9K6"; + + // mnemonic and passphrase to derive seed + var passphrase = "thisiseightbytesithink"; + var mnemonic = new Mnemonic("route clerk disease box emerge airport loud waste attitude film army tray forward deal onion eight catalog surface unit card window walnut wealth medal", Wordlist.English); + + var solKeygenWallet = new Wallet.Wallet(mnemonic, passphrase, SeedMode.Bip39); + + Console.WriteLine($"SOLLET publicKey>b58 {solKeygenWallet.Account.GetPublicKey}"); + Console.WriteLine($"SOLLET privateKey>b58 {solKeygenWallet.Account.GetPrivateKey}"); + + if (solKeygenWallet.Account.GetPublicKey != expectedSolKeygenPublicKey || solKeygenWallet.Account.GetPrivateKey != expectedSolKeygenPrivateKey) + { + Console.WriteLine("NOT GOOD FOR THE SOL"); + } + else + { + Console.WriteLine("GOOD FOR THE SOL"); + } + + } + } } \ No newline at end of file diff --git a/src/Solnet.Examples/SolletKeyGeneration.cs b/src/Solnet.Examples/SolletKeyGeneration.cs index 8f706e98..e7402787 100644 --- a/src/Solnet.Examples/SolletKeyGeneration.cs +++ b/src/Solnet.Examples/SolletKeyGeneration.cs @@ -1,45 +1,45 @@ -using NBitcoin; -using System; -using System.Collections.Generic; - -namespace Solnet.Examples -{ - public class SolletKeyGeneration - { - static void Example(string[] args) - { - var expectedSolletAddresses = new List - { - new []{"6bhhceZToGG9RsTe1nfNFXEMjavhj6CV55EsvearAt2z", "5S1UT7L6bQ8sVaPjpJyYFEEYh8HAXRXPFUEuj6kHQXs6ZE9F6a2wWrjdokAmSPP5HVP46bYxsrU8yr2FxxYmVBi6"}, - new []{"9we6kjtbcZ2vy3GSLLsZTEhbAqXPTRvEyoxa8wxSqKp5", "22J7rH3DFJb1yz8JuWUWfrpsQrNsvZKov8sznfwHbPGTznSgQ8u6LQ6KixPC2mYCJDsfzME1FbdX1x89zKq4MU3K"}, - new []{"3F2RNf2f2kWYgJ2XsqcjzVeh3rsEQnwf6cawtBiJGyKV", "5954a6aMxVnPTyMNdVKrSiqoVMRvZcwU7swGp9kHsV9HP9Eu81TebS4Mbq5ZGmZwUaJkkKoCJ2eJSY9cTdWzRXeF"}, - new []{"GyWQGKYpvzFmjhnG5Pfw9jfvgDM7LB31HnTRPopCCS9", "tUV1EeY6CARAbuEfVqKS46X136PRBea8PcmYfHRWNQc6yYB14GkSBZ6PTybUt5W14A7FSJ6Mm6NN22fLhUhDUGu"}, - new []{"GjtWSPacUQFVShQKRKPc342MLCdNiusn3WTJQKxuDfXi", "iLtErFEn6w5xbsUW63QLYMTJeX8TAgFTUDTgat3gxpaiN3AJbebv6ybtmTj1t1yvkqqY2k1uwFxaKZoCQAPcDZe"}, - new []{"DjGCyxjGxpvEo921Ad4tUUWquiRG6dziJUCk8HKZoaKK", "3uvEiJiMyXqQmELLjxV8r3E7CyRFg42LUAxzz6q7fPhzTCxCzPkaMCQ9ARpWYDNiDXhue2Uma1C7KR9AkiiWUS8y"}, - new []{"HU6aKFapq4RssJqV96rfE7vv1pepz5A5miPAMxGFso4X", "4xFZDEhhw3oVewE3UCvzLmhRWjjcqvVMxuYiETWiyaV2wJwEJ4ceDDE359NMirh43VYisViHAwsXjZ3F9fk6dAxB"}, - new []{"HunD57AAvhBiX2SxmEDMbrgQ9pcqrtRyWKy7dWPEWYkJ", "2Z5CFuVDPQXxrB3iw5g6SAnKqApE1djAqtTZDA83rLZ1NDi6z13rwDX17qdyUDCxK9nDwKAHdVuy3h6jeXspcYxA"}, - new []{"9KmfMX4Ne5ocb8C7PwjmJTWTpQTQcPhkeD2zY35mawhq", "c1BzdtL4RByNQnzcaUq3WuNLuyY4tQogGT7JWwy4YGBE8FGSgWUH8eNJFyJgXNYtwTKq4emhC4V132QX9REwujm"}, - new []{"7MrtfwpJBw2hn4eopB2CVEKR1kePJV5kKmKX3wUAFsJ9", "4skUmBVmaLoriN9Ge8xcF4xQFJmF554rnRRa2u1yDbre2zj2wUpgCXUaPETLSAWNudCkNAkWM5oJFJRaeZY1g9JR"} - }; - - // mnemonic and passphrase to derive seed - var mnemonic = new Mnemonic("route clerk disease box emerge airport loud waste attitude film army tray forward deal onion eight catalog surface unit card window walnut wealth medal", Wordlist.English); - - // The passphrase isn't used to harden the mnemonic in this case. - var solletWallet = new Wallet.Wallet(mnemonic); - var flag = true; - - // Mimic sollet key generation - for (int i = 0; i < 10; i++) - { - var account = solletWallet.GetAccount(i); - - Console.WriteLine($"SOLLET publicKey>b58 {account.GetPublicKey}"); - Console.WriteLine($"SOLLET privateKey>b58 {account.GetPrivateKey}"); - - if (account.GetPublicKey != expectedSolletAddresses[i][0] || account.GetPrivateKey != expectedSolletAddresses[i][1]) flag = false; - } - Console.WriteLine(flag ? "GOOD FOR THE SOLLET" : "NOT GOOD FOR THE SOLLET"); - } - } +using NBitcoin; +using System; +using System.Collections.Generic; + +namespace Solnet.Examples +{ + public class SolletKeyGeneration + { + static void Example(string[] args) + { + var expectedSolletAddresses = new List + { + new []{"6bhhceZToGG9RsTe1nfNFXEMjavhj6CV55EsvearAt2z", "5S1UT7L6bQ8sVaPjpJyYFEEYh8HAXRXPFUEuj6kHQXs6ZE9F6a2wWrjdokAmSPP5HVP46bYxsrU8yr2FxxYmVBi6"}, + new []{"9we6kjtbcZ2vy3GSLLsZTEhbAqXPTRvEyoxa8wxSqKp5", "22J7rH3DFJb1yz8JuWUWfrpsQrNsvZKov8sznfwHbPGTznSgQ8u6LQ6KixPC2mYCJDsfzME1FbdX1x89zKq4MU3K"}, + new []{"3F2RNf2f2kWYgJ2XsqcjzVeh3rsEQnwf6cawtBiJGyKV", "5954a6aMxVnPTyMNdVKrSiqoVMRvZcwU7swGp9kHsV9HP9Eu81TebS4Mbq5ZGmZwUaJkkKoCJ2eJSY9cTdWzRXeF"}, + new []{"GyWQGKYpvzFmjhnG5Pfw9jfvgDM7LB31HnTRPopCCS9", "tUV1EeY6CARAbuEfVqKS46X136PRBea8PcmYfHRWNQc6yYB14GkSBZ6PTybUt5W14A7FSJ6Mm6NN22fLhUhDUGu"}, + new []{"GjtWSPacUQFVShQKRKPc342MLCdNiusn3WTJQKxuDfXi", "iLtErFEn6w5xbsUW63QLYMTJeX8TAgFTUDTgat3gxpaiN3AJbebv6ybtmTj1t1yvkqqY2k1uwFxaKZoCQAPcDZe"}, + new []{"DjGCyxjGxpvEo921Ad4tUUWquiRG6dziJUCk8HKZoaKK", "3uvEiJiMyXqQmELLjxV8r3E7CyRFg42LUAxzz6q7fPhzTCxCzPkaMCQ9ARpWYDNiDXhue2Uma1C7KR9AkiiWUS8y"}, + new []{"HU6aKFapq4RssJqV96rfE7vv1pepz5A5miPAMxGFso4X", "4xFZDEhhw3oVewE3UCvzLmhRWjjcqvVMxuYiETWiyaV2wJwEJ4ceDDE359NMirh43VYisViHAwsXjZ3F9fk6dAxB"}, + new []{"HunD57AAvhBiX2SxmEDMbrgQ9pcqrtRyWKy7dWPEWYkJ", "2Z5CFuVDPQXxrB3iw5g6SAnKqApE1djAqtTZDA83rLZ1NDi6z13rwDX17qdyUDCxK9nDwKAHdVuy3h6jeXspcYxA"}, + new []{"9KmfMX4Ne5ocb8C7PwjmJTWTpQTQcPhkeD2zY35mawhq", "c1BzdtL4RByNQnzcaUq3WuNLuyY4tQogGT7JWwy4YGBE8FGSgWUH8eNJFyJgXNYtwTKq4emhC4V132QX9REwujm"}, + new []{"7MrtfwpJBw2hn4eopB2CVEKR1kePJV5kKmKX3wUAFsJ9", "4skUmBVmaLoriN9Ge8xcF4xQFJmF554rnRRa2u1yDbre2zj2wUpgCXUaPETLSAWNudCkNAkWM5oJFJRaeZY1g9JR"} + }; + + // mnemonic and passphrase to derive seed + var mnemonic = new Mnemonic("route clerk disease box emerge airport loud waste attitude film army tray forward deal onion eight catalog surface unit card window walnut wealth medal", Wordlist.English); + + // The passphrase isn't used to harden the mnemonic in this case. + var solletWallet = new Wallet.Wallet(mnemonic); + var flag = true; + + // Mimic sollet key generation + for (int i = 0; i < 10; i++) + { + var account = solletWallet.GetAccount(i); + + Console.WriteLine($"SOLLET publicKey>b58 {account.GetPublicKey}"); + Console.WriteLine($"SOLLET privateKey>b58 {account.GetPrivateKey}"); + + if (account.GetPublicKey != expectedSolletAddresses[i][0] || account.GetPrivateKey != expectedSolletAddresses[i][1]) flag = false; + } + Console.WriteLine(flag ? "GOOD FOR THE SOLLET" : "NOT GOOD FOR THE SOLLET"); + } + } } \ No newline at end of file diff --git a/src/Solnet.Examples/SolletKeygenKeystore.cs b/src/Solnet.Examples/SolletKeygenKeystore.cs index ce471aff..98e8b6e9 100644 --- a/src/Solnet.Examples/SolletKeygenKeystore.cs +++ b/src/Solnet.Examples/SolletKeygenKeystore.cs @@ -1,84 +1,84 @@ -using NBitcoin; -using Solnet.KeyStore; -using Solnet.Wallet; -using System; -using System.Collections.Generic; -using System.Text; - -namespace Solnet.Examples -{ - public class SolletKeygenKeystore - { - static void Example(string[] args) - { - var expectedSolletAddresses = new List - { - new []{"6bhhceZToGG9RsTe1nfNFXEMjavhj6CV55EsvearAt2z", "5S1UT7L6bQ8sVaPjpJyYFEEYh8HAXRXPFUEuj6kHQXs6ZE9F6a2wWrjdokAmSPP5HVP46bYxsrU8yr2FxxYmVBi6"}, - new []{"9we6kjtbcZ2vy3GSLLsZTEhbAqXPTRvEyoxa8wxSqKp5", "22J7rH3DFJb1yz8JuWUWfrpsQrNsvZKov8sznfwHbPGTznSgQ8u6LQ6KixPC2mYCJDsfzME1FbdX1x89zKq4MU3K"}, - new []{"3F2RNf2f2kWYgJ2XsqcjzVeh3rsEQnwf6cawtBiJGyKV", "5954a6aMxVnPTyMNdVKrSiqoVMRvZcwU7swGp9kHsV9HP9Eu81TebS4Mbq5ZGmZwUaJkkKoCJ2eJSY9cTdWzRXeF"}, - new []{"GyWQGKYpvzFmjhnG5Pfw9jfvgDM7LB31HnTRPopCCS9", "tUV1EeY6CARAbuEfVqKS46X136PRBea8PcmYfHRWNQc6yYB14GkSBZ6PTybUt5W14A7FSJ6Mm6NN22fLhUhDUGu"}, - new []{"GjtWSPacUQFVShQKRKPc342MLCdNiusn3WTJQKxuDfXi", "iLtErFEn6w5xbsUW63QLYMTJeX8TAgFTUDTgat3gxpaiN3AJbebv6ybtmTj1t1yvkqqY2k1uwFxaKZoCQAPcDZe"}, - new []{"DjGCyxjGxpvEo921Ad4tUUWquiRG6dziJUCk8HKZoaKK", "3uvEiJiMyXqQmELLjxV8r3E7CyRFg42LUAxzz6q7fPhzTCxCzPkaMCQ9ARpWYDNiDXhue2Uma1C7KR9AkiiWUS8y"}, - new []{"HU6aKFapq4RssJqV96rfE7vv1pepz5A5miPAMxGFso4X", "4xFZDEhhw3oVewE3UCvzLmhRWjjcqvVMxuYiETWiyaV2wJwEJ4ceDDE359NMirh43VYisViHAwsXjZ3F9fk6dAxB"}, - new []{"HunD57AAvhBiX2SxmEDMbrgQ9pcqrtRyWKy7dWPEWYkJ", "2Z5CFuVDPQXxrB3iw5g6SAnKqApE1djAqtTZDA83rLZ1NDi6z13rwDX17qdyUDCxK9nDwKAHdVuy3h6jeXspcYxA"}, - new []{"9KmfMX4Ne5ocb8C7PwjmJTWTpQTQcPhkeD2zY35mawhq", "c1BzdtL4RByNQnzcaUq3WuNLuyY4tQogGT7JWwy4YGBE8FGSgWUH8eNJFyJgXNYtwTKq4emhC4V132QX9REwujm"}, - new []{"7MrtfwpJBw2hn4eopB2CVEKR1kePJV5kKmKX3wUAFsJ9", "4skUmBVmaLoriN9Ge8xcF4xQFJmF554rnRRa2u1yDbre2zj2wUpgCXUaPETLSAWNudCkNAkWM5oJFJRaeZY1g9JR"} - }; - const string _password = "password"; - const string mnemonicWords = - "route clerk disease box emerge airport loud waste attitude film army tray forward deal onion eight catalog surface unit card window walnut wealth medal"; - - var mnemonic = new Mnemonic(mnemonicWords, Wordlist.English); - - var keystoreService = new SecretKeyStoreService(); - - // no passphrase to generate same keys as sollet - var wallet = new Wallet.Wallet(mnemonic); - var seed = wallet.DeriveMnemonicSeed(); - Account account = null; - bool flag = true; // to check if the keys are the expected ones - Console.WriteLine($"Seed: {seed.ToStringByteArray()}\nAddress: {wallet.Account.GetPublicKey}"); - - /* 1. Encrypt mnemonic derived seed and generate keystore as json - var keystoreJson = keystoreService.EncryptAndGenerateDefaultKeyStoreAsJson(_password, seed, wallet.Account.EncodedPublicKey); - */ - - /* 2. Encrypt the mnemonic as bytes */ - var stringByteArray = Encoding.UTF8.GetBytes(mnemonic.ToString()); - var encryptedKeystoreJson = keystoreService.EncryptAndGenerateDefaultKeyStoreAsJson(_password, stringByteArray, wallet.Account.GetPublicKey); - - var keystoreJsonAddr = SecretKeyStoreService.GetAddressFromKeyStore(encryptedKeystoreJson); - - Console.WriteLine($"Keystore JSON: {encryptedKeystoreJson}\nKeystore Address: {keystoreJsonAddr}"); - - /* 1. Decrypt mnemonic derived seed and generate wallet from it - var decryptedKeystore = keystoreService.DecryptKeyStoreFromJson(_password, keystoreJson); - */ - - /* 2. Decrypt the mnemonic as bytes */ - var decryptedKeystore = keystoreService.DecryptKeyStoreFromJson(_password, encryptedKeystoreJson); - var mnemonicString = Encoding.UTF8.GetString(decryptedKeystore); - - /* 2. Restore the wallet from the restored mnemonic */ - var restoredMnemonic = new Mnemonic(mnemonicString); - var restoredWallet = new Wallet.Wallet(restoredMnemonic); - - // no passphrase to generate same keys as sollet - //var restoredWallet = new Wallet.Wallet(decryptedKeystore); - var restoredSeed = restoredWallet.DeriveMnemonicSeed(); - - Console.WriteLine($"Seed: {restoredSeed.ToStringByteArray()}"); - - // Mimic sollet key generation - for (int i = 0; i < 10; i++) - { - account = restoredWallet.GetAccount(i); - - Console.WriteLine($"RESTORED SOLLET address {account.GetPublicKey}"); - - if (account.GetPublicKey != expectedSolletAddresses[i][0] || account.GetPrivateKey != expectedSolletAddresses[i][1]) flag = false; - } - Console.WriteLine(flag ? "GOOD RESTORE FOR THE SOLLET" : "NOT GOOD RESTORE FOR THE SOLLET"); - } - } +using NBitcoin; +using Solnet.KeyStore; +using Solnet.Wallet; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Solnet.Examples +{ + public class SolletKeygenKeystore + { + static void Example(string[] args) + { + var expectedSolletAddresses = new List + { + new []{"6bhhceZToGG9RsTe1nfNFXEMjavhj6CV55EsvearAt2z", "5S1UT7L6bQ8sVaPjpJyYFEEYh8HAXRXPFUEuj6kHQXs6ZE9F6a2wWrjdokAmSPP5HVP46bYxsrU8yr2FxxYmVBi6"}, + new []{"9we6kjtbcZ2vy3GSLLsZTEhbAqXPTRvEyoxa8wxSqKp5", "22J7rH3DFJb1yz8JuWUWfrpsQrNsvZKov8sznfwHbPGTznSgQ8u6LQ6KixPC2mYCJDsfzME1FbdX1x89zKq4MU3K"}, + new []{"3F2RNf2f2kWYgJ2XsqcjzVeh3rsEQnwf6cawtBiJGyKV", "5954a6aMxVnPTyMNdVKrSiqoVMRvZcwU7swGp9kHsV9HP9Eu81TebS4Mbq5ZGmZwUaJkkKoCJ2eJSY9cTdWzRXeF"}, + new []{"GyWQGKYpvzFmjhnG5Pfw9jfvgDM7LB31HnTRPopCCS9", "tUV1EeY6CARAbuEfVqKS46X136PRBea8PcmYfHRWNQc6yYB14GkSBZ6PTybUt5W14A7FSJ6Mm6NN22fLhUhDUGu"}, + new []{"GjtWSPacUQFVShQKRKPc342MLCdNiusn3WTJQKxuDfXi", "iLtErFEn6w5xbsUW63QLYMTJeX8TAgFTUDTgat3gxpaiN3AJbebv6ybtmTj1t1yvkqqY2k1uwFxaKZoCQAPcDZe"}, + new []{"DjGCyxjGxpvEo921Ad4tUUWquiRG6dziJUCk8HKZoaKK", "3uvEiJiMyXqQmELLjxV8r3E7CyRFg42LUAxzz6q7fPhzTCxCzPkaMCQ9ARpWYDNiDXhue2Uma1C7KR9AkiiWUS8y"}, + new []{"HU6aKFapq4RssJqV96rfE7vv1pepz5A5miPAMxGFso4X", "4xFZDEhhw3oVewE3UCvzLmhRWjjcqvVMxuYiETWiyaV2wJwEJ4ceDDE359NMirh43VYisViHAwsXjZ3F9fk6dAxB"}, + new []{"HunD57AAvhBiX2SxmEDMbrgQ9pcqrtRyWKy7dWPEWYkJ", "2Z5CFuVDPQXxrB3iw5g6SAnKqApE1djAqtTZDA83rLZ1NDi6z13rwDX17qdyUDCxK9nDwKAHdVuy3h6jeXspcYxA"}, + new []{"9KmfMX4Ne5ocb8C7PwjmJTWTpQTQcPhkeD2zY35mawhq", "c1BzdtL4RByNQnzcaUq3WuNLuyY4tQogGT7JWwy4YGBE8FGSgWUH8eNJFyJgXNYtwTKq4emhC4V132QX9REwujm"}, + new []{"7MrtfwpJBw2hn4eopB2CVEKR1kePJV5kKmKX3wUAFsJ9", "4skUmBVmaLoriN9Ge8xcF4xQFJmF554rnRRa2u1yDbre2zj2wUpgCXUaPETLSAWNudCkNAkWM5oJFJRaeZY1g9JR"} + }; + const string _password = "password"; + const string mnemonicWords = + "route clerk disease box emerge airport loud waste attitude film army tray forward deal onion eight catalog surface unit card window walnut wealth medal"; + + var mnemonic = new Mnemonic(mnemonicWords, Wordlist.English); + + var keystoreService = new SecretKeyStoreService(); + + // no passphrase to generate same keys as sollet + var wallet = new Wallet.Wallet(mnemonic); + var seed = wallet.DeriveMnemonicSeed(); + Account account = null; + bool flag = true; // to check if the keys are the expected ones + Console.WriteLine($"Seed: {seed.ToStringByteArray()}\nAddress: {wallet.Account.GetPublicKey}"); + + /* 1. Encrypt mnemonic derived seed and generate keystore as json + var keystoreJson = keystoreService.EncryptAndGenerateDefaultKeyStoreAsJson(_password, seed, wallet.Account.EncodedPublicKey); + */ + + /* 2. Encrypt the mnemonic as bytes */ + var stringByteArray = Encoding.UTF8.GetBytes(mnemonic.ToString()); + var encryptedKeystoreJson = keystoreService.EncryptAndGenerateDefaultKeyStoreAsJson(_password, stringByteArray, wallet.Account.GetPublicKey); + + var keystoreJsonAddr = SecretKeyStoreService.GetAddressFromKeyStore(encryptedKeystoreJson); + + Console.WriteLine($"Keystore JSON: {encryptedKeystoreJson}\nKeystore Address: {keystoreJsonAddr}"); + + /* 1. Decrypt mnemonic derived seed and generate wallet from it + var decryptedKeystore = keystoreService.DecryptKeyStoreFromJson(_password, keystoreJson); + */ + + /* 2. Decrypt the mnemonic as bytes */ + var decryptedKeystore = keystoreService.DecryptKeyStoreFromJson(_password, encryptedKeystoreJson); + var mnemonicString = Encoding.UTF8.GetString(decryptedKeystore); + + /* 2. Restore the wallet from the restored mnemonic */ + var restoredMnemonic = new Mnemonic(mnemonicString); + var restoredWallet = new Wallet.Wallet(restoredMnemonic); + + // no passphrase to generate same keys as sollet + //var restoredWallet = new Wallet.Wallet(decryptedKeystore); + var restoredSeed = restoredWallet.DeriveMnemonicSeed(); + + Console.WriteLine($"Seed: {restoredSeed.ToStringByteArray()}"); + + // Mimic sollet key generation + for (int i = 0; i < 10; i++) + { + account = restoredWallet.GetAccount(i); + + Console.WriteLine($"RESTORED SOLLET address {account.GetPublicKey}"); + + if (account.GetPublicKey != expectedSolletAddresses[i][0] || account.GetPrivateKey != expectedSolletAddresses[i][1]) flag = false; + } + Console.WriteLine(flag ? "GOOD RESTORE FOR THE SOLLET" : "NOT GOOD RESTORE FOR THE SOLLET"); + } + } } \ No newline at end of file diff --git a/src/Solnet.Examples/SolnetRpcTester.cs b/src/Solnet.Examples/SolnetRpcTester.cs index d2ddf664..f07ffb9a 100644 --- a/src/Solnet.Examples/SolnetRpcTester.cs +++ b/src/Solnet.Examples/SolnetRpcTester.cs @@ -1,112 +1,112 @@ -using Solnet.Rpc; -using System; -using System.Collections.Generic; - -namespace Solnet.Examples -{ - class SolnetRpcTester - { - static void Main(string[] args) - { - var c = ClientFactory.GetClient(Cluster.TestNet); - - //var accInfo = c.GetGenesisHash(); - - //var blockCommitment = c.GetBlockCommitment(78561320); - - //var blockTime = c.GetBlockTime(78561320); - - //var cn = c.GetClusterNodes(); - //var bh = c.GetBlockHeight(); - //Console.WriteLine(bh.Result); - //var identity = c.GetIdentity(); - //Console.WriteLine(identity.Result.Identity); - - //var inflationGov = c.GetInflationGovernor(); - //Console.WriteLine(inflationGov.Result.Terminal); - - //var inflationRate = c.GetInflationRate(); - //Console.WriteLine(inflationRate.Result.Total); - - //var perf = c.GetRecentPerformanceSamples(); - //Console.WriteLine($"Tx: {perf.Result[0].NumTransactions} Slot: {perf.Result[0].Slot }"); - - var signatures = c.GetSignaturesForAddress("4Rf9mGD7FeYknun5JczX5nGLTfQuS1GRjNVfkEMKE92b"); - - var v = c.GetVersion(); - Console.WriteLine(v.Result.SolanaCore); - Console.WriteLine(v.Result.FeatureSet); - - var va = c.GetVoteAccounts(); - Console.WriteLine(va.Result.Current.Length); - Console.WriteLine(va.Result.Delinquent.Length); - - //var last = c.GetBlock(79662905); - //var t = c.GetBlockHeight(); - //var x = c.GetBlockProduction(); - //var x2 = c.GetBlockProduction("Bbe9EKucmRtJr2J4dd5Eb5ybQmY7Fm7jYxKXxmmkLFsu"); - //var x3 = c.GetBlockProduction("Bbe9EKucmRtJr2J4dd5Eb5ybQmY7Fm7jYxKXxmmkLFsu", 79_000_000); - //var x5 = c.GetBlockProduction(79714135); - //var x4 = c.GetBlockProduction("Bbe9EKucmRtJr2J4dd5Eb5ybQmY7Fm7jYxKXxmmkLFsu", 79_000_000, 79_500_000); - - //var res = c.GetTransaction("5as3w4KMpY23MP5T1nkPVksjXjN7hnjHKqiDxRMxUNcw5XsCGtStayZib1kQdyR2D9w8dR11Ha9Xk38KP3kbAwM1"); - - //var res = c.GetBlocks(79_499_950, 79_500_000); - //var res = c.GetBlocksWithLimit(79_699_950, 2); - //var res = c.GetFirstAvailableBlock(); - //var res = c.GetHealth(); - - //var res = c.GetLeaderSchedule(); - //var res2 = c.GetLeaderSchedule(79_700_000); - //var res3 = c.GetLeaderSchedule(identity: "Bbe9EKucmRtJr2J4dd5Eb5ybQmY7Fm7jYxKXxmmkLFsu"); - //var res4 = c.GetLeaderSchedule(79_700_000, "Bbe9EKucmRtJr2J4dd5Eb5ybQmY7Fm7jYxKXxmmkLFsu"); - var inflation = c.GetInflationReward( - new List { "25xzEf8cqLLEm2wyZTEBtCDchsUFm3SVESjs6eEFHJWe", "GPQdoUUDQXM1gWgRVwBbYmDqAgxoZN3bhVeKr1P8jd4c" }); - Console.WriteLine(inflation.Result.Count); - var res = c.GetLargestAccounts("circulating"); - Console.WriteLine(res.Result.Value.Count); - - var accs = c.GetMultipleAccounts(new List { "Bbe9EKucmRtJr2J4dd5Eb5ybQmY7Fm7jYxKXxmmkLFsu", "9we6kjtbcZ2vy3GSLLsZTEhbAqXPTRvEyoxa8wxSqKp5" }); - - - /* Large accounts for Token Mint PubKey - var largeAccounts = c.GetTokenLargestAccounts("7ugkvt26sFjMdiFQFP5AQX8m8UkxWaW7rk2nBk4R6Gf2"); - - foreach (var acc in largeAccounts.Result.Value) - { - Console.WriteLine($"Acc: {acc.Address} Balance: {acc.UiAmountString}"); - } - */ - - /* Token balance for Account PubKey - var tokenBalance = c.GetTokenAccountBalance("7247amxcSBamBSKZJrqbj373CiJSa1v21cRav56C3WfZ"); - Console.WriteLine($"Token Balance: {tokenBalance.Result.Value.UiAmountString}"); - */ - - //var tokenAccounts = c.GetTokenAccountsByOwner( - // "9we6kjtbcZ2vy3GSLLsZTEhbAqXPTRvEyoxa8wxSqKp5",null, "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"); - //foreach (var acc in tokenAccounts.Result.Value) - //{ - // Console.WriteLine("--------------------------------------------------------------------"); - // var accInfo = c.GetAccountInfo(acc.PublicKey, BinaryEncoding.JsonParsed); - // TokenAccountData tokenAccData = null; - // var data = accInfo.Result.Value.TryGetAccountData(out tokenAccData); - // Console.WriteLine( - // $"Token Account:\n" + - // $"\tAccount PubKey: {acc.PublicKey}\n" + - // $"\tAccount Lamport Balance: {acc.Account.Lamports}\n" + - // $"\tAccount Encoded Data: {acc.Account.Data}\n" + - // $"Account Info for {acc.PublicKey}:\n" + - // $"\tAccount Owner: {tokenAccData.Parsed.Info.Owner}\n" + - // $"\tToken Balance: {tokenAccData.Parsed.Info.TokenAmount.UiAmountString}\n" + - // $"\tToken Mint: {tokenAccData.Parsed.Info.Mint}" - // ); - // Console.WriteLine("--------------------------------------------------------------------"); - //} - var tokenAccounts = c.GetTokenAccountsByOwner( - "9we6kjtbcZ2vy3GSLLsZTEhbAqXPTRvEyoxa8wxSqKp5", tokenProgramId: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"); - - Console.ReadKey(); - } - } +using Solnet.Rpc; +using System; +using System.Collections.Generic; + +namespace Solnet.Examples +{ + class SolnetRpcTester + { + static void Main(string[] args) + { + var c = ClientFactory.GetClient(Cluster.TestNet); + + //var accInfo = c.GetGenesisHash(); + + //var blockCommitment = c.GetBlockCommitment(78561320); + + //var blockTime = c.GetBlockTime(78561320); + + //var cn = c.GetClusterNodes(); + //var bh = c.GetBlockHeight(); + //Console.WriteLine(bh.Result); + //var identity = c.GetIdentity(); + //Console.WriteLine(identity.Result.Identity); + + //var inflationGov = c.GetInflationGovernor(); + //Console.WriteLine(inflationGov.Result.Terminal); + + //var inflationRate = c.GetInflationRate(); + //Console.WriteLine(inflationRate.Result.Total); + + //var perf = c.GetRecentPerformanceSamples(); + //Console.WriteLine($"Tx: {perf.Result[0].NumTransactions} Slot: {perf.Result[0].Slot }"); + + var signatures = c.GetSignaturesForAddress("4Rf9mGD7FeYknun5JczX5nGLTfQuS1GRjNVfkEMKE92b"); + + var v = c.GetVersion(); + Console.WriteLine(v.Result.SolanaCore); + Console.WriteLine(v.Result.FeatureSet); + + var va = c.GetVoteAccounts(); + Console.WriteLine(va.Result.Current.Length); + Console.WriteLine(va.Result.Delinquent.Length); + + //var last = c.GetBlock(79662905); + //var t = c.GetBlockHeight(); + //var x = c.GetBlockProduction(); + //var x2 = c.GetBlockProduction("Bbe9EKucmRtJr2J4dd5Eb5ybQmY7Fm7jYxKXxmmkLFsu"); + //var x3 = c.GetBlockProduction("Bbe9EKucmRtJr2J4dd5Eb5ybQmY7Fm7jYxKXxmmkLFsu", 79_000_000); + //var x5 = c.GetBlockProduction(79714135); + //var x4 = c.GetBlockProduction("Bbe9EKucmRtJr2J4dd5Eb5ybQmY7Fm7jYxKXxmmkLFsu", 79_000_000, 79_500_000); + + //var res = c.GetTransaction("5as3w4KMpY23MP5T1nkPVksjXjN7hnjHKqiDxRMxUNcw5XsCGtStayZib1kQdyR2D9w8dR11Ha9Xk38KP3kbAwM1"); + + //var res = c.GetBlocks(79_499_950, 79_500_000); + //var res = c.GetBlocksWithLimit(79_699_950, 2); + //var res = c.GetFirstAvailableBlock(); + //var res = c.GetHealth(); + + //var res = c.GetLeaderSchedule(); + //var res2 = c.GetLeaderSchedule(79_700_000); + //var res3 = c.GetLeaderSchedule(identity: "Bbe9EKucmRtJr2J4dd5Eb5ybQmY7Fm7jYxKXxmmkLFsu"); + //var res4 = c.GetLeaderSchedule(79_700_000, "Bbe9EKucmRtJr2J4dd5Eb5ybQmY7Fm7jYxKXxmmkLFsu"); + var inflation = c.GetInflationReward( + new List { "25xzEf8cqLLEm2wyZTEBtCDchsUFm3SVESjs6eEFHJWe", "GPQdoUUDQXM1gWgRVwBbYmDqAgxoZN3bhVeKr1P8jd4c" }); + Console.WriteLine(inflation.Result.Count); + var res = c.GetLargestAccounts("circulating"); + Console.WriteLine(res.Result.Value.Count); + + var accs = c.GetMultipleAccounts(new List { "Bbe9EKucmRtJr2J4dd5Eb5ybQmY7Fm7jYxKXxmmkLFsu", "9we6kjtbcZ2vy3GSLLsZTEhbAqXPTRvEyoxa8wxSqKp5" }); + + + /* Large accounts for Token Mint PubKey + var largeAccounts = c.GetTokenLargestAccounts("7ugkvt26sFjMdiFQFP5AQX8m8UkxWaW7rk2nBk4R6Gf2"); + + foreach (var acc in largeAccounts.Result.Value) + { + Console.WriteLine($"Acc: {acc.Address} Balance: {acc.UiAmountString}"); + } + */ + + /* Token balance for Account PubKey + var tokenBalance = c.GetTokenAccountBalance("7247amxcSBamBSKZJrqbj373CiJSa1v21cRav56C3WfZ"); + Console.WriteLine($"Token Balance: {tokenBalance.Result.Value.UiAmountString}"); + */ + + //var tokenAccounts = c.GetTokenAccountsByOwner( + // "9we6kjtbcZ2vy3GSLLsZTEhbAqXPTRvEyoxa8wxSqKp5",null, "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"); + //foreach (var acc in tokenAccounts.Result.Value) + //{ + // Console.WriteLine("--------------------------------------------------------------------"); + // var accInfo = c.GetAccountInfo(acc.PublicKey, BinaryEncoding.JsonParsed); + // TokenAccountData tokenAccData = null; + // var data = accInfo.Result.Value.TryGetAccountData(out tokenAccData); + // Console.WriteLine( + // $"Token Account:\n" + + // $"\tAccount PubKey: {acc.PublicKey}\n" + + // $"\tAccount Lamport Balance: {acc.Account.Lamports}\n" + + // $"\tAccount Encoded Data: {acc.Account.Data}\n" + + // $"Account Info for {acc.PublicKey}:\n" + + // $"\tAccount Owner: {tokenAccData.Parsed.Info.Owner}\n" + + // $"\tToken Balance: {tokenAccData.Parsed.Info.TokenAmount.UiAmountString}\n" + + // $"\tToken Mint: {tokenAccData.Parsed.Info.Mint}" + // ); + // Console.WriteLine("--------------------------------------------------------------------"); + //} + var tokenAccounts = c.GetTokenAccountsByOwner( + "9we6kjtbcZ2vy3GSLLsZTEhbAqXPTRvEyoxa8wxSqKp5", tokenProgramId: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"); + + Console.ReadKey(); + } + } } \ No newline at end of file diff --git a/src/Solnet.Examples/SolnetStreamingRpcTester.cs b/src/Solnet.Examples/SolnetStreamingRpcTester.cs index 0a19de01..68abc5fe 100644 --- a/src/Solnet.Examples/SolnetStreamingRpcTester.cs +++ b/src/Solnet.Examples/SolnetStreamingRpcTester.cs @@ -1,46 +1,46 @@ -using NBitcoin.DataEncoders; -using Solnet.Rpc; -using Solnet.Rpc.Core.Sockets; -using System; - -namespace Solnet.Examples -{ - public class SolnetStreamingRpcTester - { - static void Example(string[] args) - { - IStreamingRpcClient c = ClientFactory.GetStreamingClient(Cluster.MainNet); - var b64Dec = new Base64Encoder(); - c.Init().Wait(); - - var sub = c.SubscribeAccountInfo( - "4tSvZvnbyzHXLMTiFonMyxZoHmFqau1XArcRCVHLZ5gX", - (s, data) => - { - // In a case where account data is received as jsonParsed - //TokenAccountData tokenAcc = null; - //var accData = data.Value.TryGetAccountData(out tokenAcc); - //if (accData) - // Console.WriteLine( - // $"Channel: {s.Channel} Slot: {data.Context.Slot} Lamports: {data.Value.Lamports} Account Owner: {tokenAcc.Parsed.Info.Owner}"); - - //// In a case where account data is received as base64 - //string encodedData = null; - //var dataString = data.Value.TryGetAccountData(out encodedData); - //if (dataString) - // Console.WriteLine( - // $"Channel: {s.Channel} Slot: {data.Context.Slot} Lamports: {data.Value.Lamports} AccountData: {encodedData}"); - }); - - sub.SubscriptionChanged += SubscriptionChanged; - - Console.ReadKey(); - Console.ReadKey(); - } - - private static void SubscriptionChanged(object sender, SubscriptionEvent e) - { - Console.WriteLine("Subscription changed to: " + e.Status); - } - } +using NBitcoin.DataEncoders; +using Solnet.Rpc; +using Solnet.Rpc.Core.Sockets; +using System; + +namespace Solnet.Examples +{ + public class SolnetStreamingRpcTester + { + static void Example(string[] args) + { + IStreamingRpcClient c = ClientFactory.GetStreamingClient(Cluster.MainNet); + var b64Dec = new Base64Encoder(); + c.Init().Wait(); + + var sub = c.SubscribeAccountInfo( + "4tSvZvnbyzHXLMTiFonMyxZoHmFqau1XArcRCVHLZ5gX", + (s, data) => + { + // In a case where account data is received as jsonParsed + //TokenAccountData tokenAcc = null; + //var accData = data.Value.TryGetAccountData(out tokenAcc); + //if (accData) + // Console.WriteLine( + // $"Channel: {s.Channel} Slot: {data.Context.Slot} Lamports: {data.Value.Lamports} Account Owner: {tokenAcc.Parsed.Info.Owner}"); + + //// In a case where account data is received as base64 + //string encodedData = null; + //var dataString = data.Value.TryGetAccountData(out encodedData); + //if (dataString) + // Console.WriteLine( + // $"Channel: {s.Channel} Slot: {data.Context.Slot} Lamports: {data.Value.Lamports} AccountData: {encodedData}"); + }); + + sub.SubscriptionChanged += SubscriptionChanged; + + Console.ReadKey(); + Console.ReadKey(); + } + + private static void SubscriptionChanged(object sender, SubscriptionEvent e) + { + Console.WriteLine("Subscription changed to: " + e.Status); + } + } } \ No newline at end of file diff --git a/src/Solnet.Examples/TransactionBuilderExample.cs b/src/Solnet.Examples/TransactionBuilderExample.cs index 4ca3ea99..f6b02f37 100644 --- a/src/Solnet.Examples/TransactionBuilderExample.cs +++ b/src/Solnet.Examples/TransactionBuilderExample.cs @@ -1,220 +1,220 @@ -using Solnet.Programs; -using Solnet.Rpc; -using Solnet.Rpc.Builders; -using Solnet.Wallet; -using System; -using System.Collections.Generic; - -namespace Solnet.Examples -{ - public class TransactionBuilderExample - { - private static readonly IRpcClient rpcClient = ClientFactory.GetClient(Cluster.TestNet); - - private const string MnemonicWords = - "route clerk disease box emerge airport loud waste attitude film army tray " + - "forward deal onion eight catalog surface unit card window walnut wealth medal"; - - public static string PrettyPrintTransactionSimulationLogs(string[] logMessages) - { - var logString = ""; - foreach (var log in logMessages) - { - logString += $"\t\t{log}\n"; - } - - return logString; - } - - static void TransactionAndMemoExample(string[] args) - { - var rpcClient = ClientFactory.GetClient(Cluster.TestNet); - var wallet = new Wallet.Wallet(MnemonicWords); - - var fromAccount = wallet.GetAccount(10); - var toAccount = wallet.GetAccount(8); - - var blockHash = rpcClient.GetRecentBlockHash(); - //Console.WriteLine($"BlockHash >> {blockHash.Blockhash}"); - - var tx = new TransactionBuilder().SetRecentBlockHash(blockHash.Result.Value.Blockhash) - .AddInstruction(SystemProgram.Transfer(fromAccount.GetPublicKey, toAccount.GetPublicKey, 10000000)) - .AddInstruction(MemoProgram.NewMemo(fromAccount, "Hello from Sol.Net :)")).Build(fromAccount); - - Console.WriteLine($"Tx base64: {Convert.ToBase64String(tx)}"); - var txSim = rpcClient.SimulateTransaction(tx); - var logs = PrettyPrintTransactionSimulationLogs(txSim.Result.Value.Logs); - Console.WriteLine($"Transaction Simulation:\n\tError: {txSim.Result.Value.Error}\n\tLogs: \n" + logs); - var firstSig = rpcClient.SendTransaction(tx); - Console.WriteLine($"First Tx Signature: {firstSig.Result}"); - } - - static void CreateInitializeAndMintToExample(string[] args) - { - var wallet = new Wallet.Wallet(MnemonicWords); - - var blockHash = rpcClient.GetRecentBlockHash(); - var minBalanceForExemptionAcc = - rpcClient.GetMinimumBalanceForRentExemption(SystemProgram.AccountDataSize).Result; - Console.WriteLine($"MinBalanceForRentExemption Account >> {minBalanceForExemptionAcc}"); - - var minBalanceForExemptionMint = - rpcClient.GetMinimumBalanceForRentExemption(TokenProgram.MintAccountDataSize).Result; - Console.WriteLine($"MinBalanceForRentExemption Mint Account >> {minBalanceForExemptionMint}"); - - var mintAccount = wallet.GetAccount(21); - Console.WriteLine($"MintAccount: {mintAccount.GetPublicKey}"); - var ownerAccount = wallet.GetAccount(10); - Console.WriteLine($"OwnerAccount: {ownerAccount.GetPublicKey}"); - var initialAccount = wallet.GetAccount(22); - Console.WriteLine($"InitialAccount: {initialAccount.GetPublicKey}"); - - var tx = new TransactionBuilder().SetRecentBlockHash(blockHash.Result.Value.Blockhash).AddInstruction( - SystemProgram.CreateAccount( - ownerAccount.GetPublicKey, - mintAccount.GetPublicKey, - (long)minBalanceForExemptionMint, - TokenProgram.MintAccountDataSize, - TokenProgram.ProgramId)).AddInstruction( - TokenProgram.InitializeMint( - mintAccount.GetPublicKey, - 2, - ownerAccount.GetPublicKey, - ownerAccount.GetPublicKey)).AddInstruction( - SystemProgram.CreateAccount( - ownerAccount.GetPublicKey, - initialAccount.GetPublicKey, - (long)minBalanceForExemptionAcc, - SystemProgram.AccountDataSize, - TokenProgram.ProgramId)).AddInstruction( - TokenProgram.InitializeAccount( - initialAccount.GetPublicKey, - mintAccount.GetPublicKey, - ownerAccount.GetPublicKey)).AddInstruction( - TokenProgram.MintTo( - mintAccount.GetPublicKey, - initialAccount.GetPublicKey, - 25000, - ownerAccount.GetPublicKey)).AddInstruction( - MemoProgram.NewMemo( - initialAccount, - "Hello from Sol.Net")).Build(new List { ownerAccount, mintAccount, initialAccount }); - - Console.WriteLine($"Tx: {Convert.ToBase64String(tx)}"); - - var txSim = rpcClient.SimulateTransaction(tx); - var logs = PrettyPrintTransactionSimulationLogs(txSim.Result.Value.Logs); - Console.WriteLine($"Transaction Simulation:\n\tError: {txSim.Result.Value.Error}\n\tLogs: \n" + logs); - - var txReq = rpcClient.SendTransaction(tx); - Console.WriteLine($"Tx Signature: {txReq.Result}"); - } - - - static void TransferTokenExample(string[] args) - { - var wallet = new Wallet.Wallet(MnemonicWords); - - var blockHash = rpcClient.GetRecentBlockHash(); - var minBalanceForExemptionAcc = - rpcClient.GetMinimumBalanceForRentExemption(SystemProgram.AccountDataSize).Result; - Console.WriteLine($"MinBalanceForRentExemption Account >> {minBalanceForExemptionAcc}"); - - var minBalanceForExemptionMint = - rpcClient.GetMinimumBalanceForRentExemption(TokenProgram.MintAccountDataSize).Result; - Console.WriteLine($"MinBalanceForRentExemption Mint Account >> {minBalanceForExemptionMint}"); - - var mintAccount = wallet.GetAccount(21); - Console.WriteLine($"MintAccount: {mintAccount.GetPublicKey}"); - var ownerAccount = wallet.GetAccount(10); - Console.WriteLine($"OwnerAccount: {ownerAccount.GetPublicKey}"); - var initialAccount = wallet.GetAccount(24); - Console.WriteLine($"InitialAccount: {initialAccount.GetPublicKey}"); - var newAccount = wallet.GetAccount(26); - Console.WriteLine($"NewAccount: {newAccount.GetPublicKey}"); - - var tx = new TransactionBuilder().SetRecentBlockHash(blockHash.Result.Value.Blockhash).AddInstruction( - SystemProgram.CreateAccount( - ownerAccount.GetPublicKey, - newAccount.GetPublicKey, - (long)minBalanceForExemptionAcc, - SystemProgram.AccountDataSize, - TokenProgram.ProgramId)).AddInstruction( - TokenProgram.InitializeAccount( - newAccount.GetPublicKey, - mintAccount.GetPublicKey, - ownerAccount.GetPublicKey)).AddInstruction( - TokenProgram.Transfer( - initialAccount.GetPublicKey, - newAccount.GetPublicKey, - 25000, - ownerAccount.GetPublicKey)).AddInstruction( - MemoProgram.NewMemo( - initialAccount, - "Hello from Sol.Net")).Build(new List { ownerAccount, newAccount }); - - Console.WriteLine($"Tx: {Convert.ToBase64String(tx)}"); - - var txSim = rpcClient.SimulateTransaction(tx); - var logs = PrettyPrintTransactionSimulationLogs(txSim.Result.Value.Logs); - Console.WriteLine($"Transaction Simulation:\n\tError: {txSim.Result.Value.Error}\n\tLogs: \n" + logs); - - var txReq = rpcClient.SendTransaction(tx); - Console.WriteLine($"Tx Signature: {txReq.Result}"); - } - - static void TransferTokenChecked(string[] args) - { - var wallet = new Wallet.Wallet(MnemonicWords); - - var blockHash = rpcClient.GetRecentBlockHash(); - var minBalanceForExemptionAcc = - rpcClient.GetMinimumBalanceForRentExemption(SystemProgram.AccountDataSize).Result; - Console.WriteLine($"MinBalanceForRentExemption Account >> {minBalanceForExemptionAcc}"); - - var minBalanceForExemptionMint = - rpcClient.GetMinimumBalanceForRentExemption(TokenProgram.MintAccountDataSize).Result; - Console.WriteLine($"MinBalanceForRentExemption Mint Account >> {minBalanceForExemptionMint}"); - - var mintAccount = wallet.GetAccount(21); - Console.WriteLine($"MintAccount: {mintAccount.GetPublicKey}"); - var ownerAccount = wallet.GetAccount(10); - Console.WriteLine($"OwnerAccount: {ownerAccount.GetPublicKey}"); - var initialAccount = wallet.GetAccount(26); - Console.WriteLine($"InitialAccount: {initialAccount.GetPublicKey}"); - var newAccount = wallet.GetAccount(27); - Console.WriteLine($"NewAccount: {newAccount.GetPublicKey}"); - - var tx = new TransactionBuilder().SetRecentBlockHash(blockHash.Result.Value.Blockhash).AddInstruction( - SystemProgram.CreateAccount( - ownerAccount.GetPublicKey, - newAccount.GetPublicKey, - (long)minBalanceForExemptionAcc, - SystemProgram.AccountDataSize, - TokenProgram.ProgramId)).AddInstruction( - TokenProgram.InitializeAccount( - newAccount.GetPublicKey, - mintAccount.GetPublicKey, - ownerAccount.GetPublicKey)).AddInstruction( - TokenProgram.TransferChecked( - initialAccount.GetPublicKey, - newAccount.GetPublicKey, - 25000, - 2, - ownerAccount.GetPublicKey, - mintAccount.GetPublicKey)).AddInstruction( - MemoProgram.NewMemo( - initialAccount, - "Hello from Sol.Net")).Build(new List { ownerAccount, newAccount }); - - Console.WriteLine($"Tx: {Convert.ToBase64String(tx)}"); - - var txSim = rpcClient.SimulateTransaction(tx); - var logs = PrettyPrintTransactionSimulationLogs(txSim.Result.Value.Logs); - Console.WriteLine($"Transaction Simulation:\n\tError: {txSim.Result.Value.Error}\n\tLogs: \n" + logs); - - var txReq = rpcClient.SendTransaction(tx); - Console.WriteLine($"Tx Signature: {txReq.Result}"); - } - } +using Solnet.Programs; +using Solnet.Rpc; +using Solnet.Rpc.Builders; +using Solnet.Wallet; +using System; +using System.Collections.Generic; + +namespace Solnet.Examples +{ + public class TransactionBuilderExample + { + private static readonly IRpcClient rpcClient = ClientFactory.GetClient(Cluster.TestNet); + + private const string MnemonicWords = + "route clerk disease box emerge airport loud waste attitude film army tray " + + "forward deal onion eight catalog surface unit card window walnut wealth medal"; + + public static string PrettyPrintTransactionSimulationLogs(string[] logMessages) + { + var logString = ""; + foreach (var log in logMessages) + { + logString += $"\t\t{log}\n"; + } + + return logString; + } + + static void TransactionAndMemoExample(string[] args) + { + var rpcClient = ClientFactory.GetClient(Cluster.TestNet); + var wallet = new Wallet.Wallet(MnemonicWords); + + var fromAccount = wallet.GetAccount(10); + var toAccount = wallet.GetAccount(8); + + var blockHash = rpcClient.GetRecentBlockHash(); + //Console.WriteLine($"BlockHash >> {blockHash.Blockhash}"); + + var tx = new TransactionBuilder().SetRecentBlockHash(blockHash.Result.Value.Blockhash) + .AddInstruction(SystemProgram.Transfer(fromAccount.GetPublicKey, toAccount.GetPublicKey, 10000000)) + .AddInstruction(MemoProgram.NewMemo(fromAccount, "Hello from Sol.Net :)")).Build(fromAccount); + + Console.WriteLine($"Tx base64: {Convert.ToBase64String(tx)}"); + var txSim = rpcClient.SimulateTransaction(tx); + var logs = PrettyPrintTransactionSimulationLogs(txSim.Result.Value.Logs); + Console.WriteLine($"Transaction Simulation:\n\tError: {txSim.Result.Value.Error}\n\tLogs: \n" + logs); + var firstSig = rpcClient.SendTransaction(tx); + Console.WriteLine($"First Tx Signature: {firstSig.Result}"); + } + + static void CreateInitializeAndMintToExample(string[] args) + { + var wallet = new Wallet.Wallet(MnemonicWords); + + var blockHash = rpcClient.GetRecentBlockHash(); + var minBalanceForExemptionAcc = + rpcClient.GetMinimumBalanceForRentExemption(SystemProgram.AccountDataSize).Result; + Console.WriteLine($"MinBalanceForRentExemption Account >> {minBalanceForExemptionAcc}"); + + var minBalanceForExemptionMint = + rpcClient.GetMinimumBalanceForRentExemption(TokenProgram.MintAccountDataSize).Result; + Console.WriteLine($"MinBalanceForRentExemption Mint Account >> {minBalanceForExemptionMint}"); + + var mintAccount = wallet.GetAccount(21); + Console.WriteLine($"MintAccount: {mintAccount.GetPublicKey}"); + var ownerAccount = wallet.GetAccount(10); + Console.WriteLine($"OwnerAccount: {ownerAccount.GetPublicKey}"); + var initialAccount = wallet.GetAccount(22); + Console.WriteLine($"InitialAccount: {initialAccount.GetPublicKey}"); + + var tx = new TransactionBuilder().SetRecentBlockHash(blockHash.Result.Value.Blockhash).AddInstruction( + SystemProgram.CreateAccount( + ownerAccount.GetPublicKey, + mintAccount.GetPublicKey, + (long)minBalanceForExemptionMint, + TokenProgram.MintAccountDataSize, + TokenProgram.ProgramId)).AddInstruction( + TokenProgram.InitializeMint( + mintAccount.GetPublicKey, + 2, + ownerAccount.GetPublicKey, + ownerAccount.GetPublicKey)).AddInstruction( + SystemProgram.CreateAccount( + ownerAccount.GetPublicKey, + initialAccount.GetPublicKey, + (long)minBalanceForExemptionAcc, + SystemProgram.AccountDataSize, + TokenProgram.ProgramId)).AddInstruction( + TokenProgram.InitializeAccount( + initialAccount.GetPublicKey, + mintAccount.GetPublicKey, + ownerAccount.GetPublicKey)).AddInstruction( + TokenProgram.MintTo( + mintAccount.GetPublicKey, + initialAccount.GetPublicKey, + 25000, + ownerAccount.GetPublicKey)).AddInstruction( + MemoProgram.NewMemo( + initialAccount, + "Hello from Sol.Net")).Build(new List { ownerAccount, mintAccount, initialAccount }); + + Console.WriteLine($"Tx: {Convert.ToBase64String(tx)}"); + + var txSim = rpcClient.SimulateTransaction(tx); + var logs = PrettyPrintTransactionSimulationLogs(txSim.Result.Value.Logs); + Console.WriteLine($"Transaction Simulation:\n\tError: {txSim.Result.Value.Error}\n\tLogs: \n" + logs); + + var txReq = rpcClient.SendTransaction(tx); + Console.WriteLine($"Tx Signature: {txReq.Result}"); + } + + + static void TransferTokenExample(string[] args) + { + var wallet = new Wallet.Wallet(MnemonicWords); + + var blockHash = rpcClient.GetRecentBlockHash(); + var minBalanceForExemptionAcc = + rpcClient.GetMinimumBalanceForRentExemption(SystemProgram.AccountDataSize).Result; + Console.WriteLine($"MinBalanceForRentExemption Account >> {minBalanceForExemptionAcc}"); + + var minBalanceForExemptionMint = + rpcClient.GetMinimumBalanceForRentExemption(TokenProgram.MintAccountDataSize).Result; + Console.WriteLine($"MinBalanceForRentExemption Mint Account >> {minBalanceForExemptionMint}"); + + var mintAccount = wallet.GetAccount(21); + Console.WriteLine($"MintAccount: {mintAccount.GetPublicKey}"); + var ownerAccount = wallet.GetAccount(10); + Console.WriteLine($"OwnerAccount: {ownerAccount.GetPublicKey}"); + var initialAccount = wallet.GetAccount(24); + Console.WriteLine($"InitialAccount: {initialAccount.GetPublicKey}"); + var newAccount = wallet.GetAccount(26); + Console.WriteLine($"NewAccount: {newAccount.GetPublicKey}"); + + var tx = new TransactionBuilder().SetRecentBlockHash(blockHash.Result.Value.Blockhash).AddInstruction( + SystemProgram.CreateAccount( + ownerAccount.GetPublicKey, + newAccount.GetPublicKey, + (long)minBalanceForExemptionAcc, + SystemProgram.AccountDataSize, + TokenProgram.ProgramId)).AddInstruction( + TokenProgram.InitializeAccount( + newAccount.GetPublicKey, + mintAccount.GetPublicKey, + ownerAccount.GetPublicKey)).AddInstruction( + TokenProgram.Transfer( + initialAccount.GetPublicKey, + newAccount.GetPublicKey, + 25000, + ownerAccount.GetPublicKey)).AddInstruction( + MemoProgram.NewMemo( + initialAccount, + "Hello from Sol.Net")).Build(new List { ownerAccount, newAccount }); + + Console.WriteLine($"Tx: {Convert.ToBase64String(tx)}"); + + var txSim = rpcClient.SimulateTransaction(tx); + var logs = PrettyPrintTransactionSimulationLogs(txSim.Result.Value.Logs); + Console.WriteLine($"Transaction Simulation:\n\tError: {txSim.Result.Value.Error}\n\tLogs: \n" + logs); + + var txReq = rpcClient.SendTransaction(tx); + Console.WriteLine($"Tx Signature: {txReq.Result}"); + } + + static void TransferTokenChecked(string[] args) + { + var wallet = new Wallet.Wallet(MnemonicWords); + + var blockHash = rpcClient.GetRecentBlockHash(); + var minBalanceForExemptionAcc = + rpcClient.GetMinimumBalanceForRentExemption(SystemProgram.AccountDataSize).Result; + Console.WriteLine($"MinBalanceForRentExemption Account >> {minBalanceForExemptionAcc}"); + + var minBalanceForExemptionMint = + rpcClient.GetMinimumBalanceForRentExemption(TokenProgram.MintAccountDataSize).Result; + Console.WriteLine($"MinBalanceForRentExemption Mint Account >> {minBalanceForExemptionMint}"); + + var mintAccount = wallet.GetAccount(21); + Console.WriteLine($"MintAccount: {mintAccount.GetPublicKey}"); + var ownerAccount = wallet.GetAccount(10); + Console.WriteLine($"OwnerAccount: {ownerAccount.GetPublicKey}"); + var initialAccount = wallet.GetAccount(26); + Console.WriteLine($"InitialAccount: {initialAccount.GetPublicKey}"); + var newAccount = wallet.GetAccount(27); + Console.WriteLine($"NewAccount: {newAccount.GetPublicKey}"); + + var tx = new TransactionBuilder().SetRecentBlockHash(blockHash.Result.Value.Blockhash).AddInstruction( + SystemProgram.CreateAccount( + ownerAccount.GetPublicKey, + newAccount.GetPublicKey, + (long)minBalanceForExemptionAcc, + SystemProgram.AccountDataSize, + TokenProgram.ProgramId)).AddInstruction( + TokenProgram.InitializeAccount( + newAccount.GetPublicKey, + mintAccount.GetPublicKey, + ownerAccount.GetPublicKey)).AddInstruction( + TokenProgram.TransferChecked( + initialAccount.GetPublicKey, + newAccount.GetPublicKey, + 25000, + 2, + ownerAccount.GetPublicKey, + mintAccount.GetPublicKey)).AddInstruction( + MemoProgram.NewMemo( + initialAccount, + "Hello from Sol.Net")).Build(new List { ownerAccount, newAccount }); + + Console.WriteLine($"Tx: {Convert.ToBase64String(tx)}"); + + var txSim = rpcClient.SimulateTransaction(tx); + var logs = PrettyPrintTransactionSimulationLogs(txSim.Result.Value.Logs); + Console.WriteLine($"Transaction Simulation:\n\tError: {txSim.Result.Value.Error}\n\tLogs: \n" + logs); + + var txReq = rpcClient.SendTransaction(tx); + Console.WriteLine($"Tx Signature: {txReq.Result}"); + } + } } \ No newline at end of file diff --git a/src/Solnet.KeyStore/Crypto/IRandomBytesGenerator.cs b/src/Solnet.KeyStore/Crypto/IRandomBytesGenerator.cs index a6d3dc1e..631f1923 100644 --- a/src/Solnet.KeyStore/Crypto/IRandomBytesGenerator.cs +++ b/src/Solnet.KeyStore/Crypto/IRandomBytesGenerator.cs @@ -1,8 +1,8 @@ -namespace Solnet.KeyStore.Crypto -{ - public interface IRandomBytesGenerator - { - byte[] GenerateRandomInitializationVector(); - byte[] GenerateRandomSalt(); - } +namespace Solnet.KeyStore.Crypto +{ + public interface IRandomBytesGenerator + { + byte[] GenerateRandomInitializationVector(); + byte[] GenerateRandomSalt(); + } } \ No newline at end of file diff --git a/src/Solnet.KeyStore/Crypto/KeyStoreCrypto.cs b/src/Solnet.KeyStore/Crypto/KeyStoreCrypto.cs index afa5608e..1ae0e800 100644 --- a/src/Solnet.KeyStore/Crypto/KeyStoreCrypto.cs +++ b/src/Solnet.KeyStore/Crypto/KeyStoreCrypto.cs @@ -1,113 +1,113 @@ -using Org.BouncyCastle.Crypto; -using Org.BouncyCastle.Crypto.Digests; -using Org.BouncyCastle.Crypto.Generators; -using Org.BouncyCastle.Crypto.Parameters; -using Org.BouncyCastle.Security; -using Solnet.KeyStore.Exceptions; -using System; -using System.Text; - -namespace Solnet.KeyStore.Crypto -{ - //https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition - public class KeyStoreCrypto - { - public byte[] GenerateDerivedScryptKey(byte[] password, byte[] salt, int n, int r, int p, int dkLen, bool checkRandN = false) - { - if (checkRandN) - { - if (r == 1 && n >= 65536) - { - throw new ArgumentException("Cost parameter N must be > 1 and < 65536."); - } - } - - return Scrypt.CryptoScrypt(password, salt, n, r, p, dkLen); - } - - public byte[] GenerateCipherKey(byte[] derivedKey) - { - var cypherKey = new byte[16]; - Array.Copy(derivedKey, cypherKey, 16); - return cypherKey; - } - - public byte[] CalculateKeccakHash(byte[] value) - { - var digest = new KeccakDigest(256); - var output = new byte[digest.GetDigestSize()]; - digest.BlockUpdate(value, 0, value.Length); - digest.DoFinal(output, 0); - return output; - } - - public byte[] GenerateMac(byte[] derivedKey, byte[] cipherText) - { - var result = new byte[16 + cipherText.Length]; - Array.Copy(derivedKey, 16, result, 0, 16); - Array.Copy(cipherText, 0, result, 16, cipherText.Length); - return CalculateKeccakHash(result); - } - - public byte[] GeneratePbkdf2Sha256DerivedKey(string password, byte[] salt, int count, int dklen) - { - var pdb = new Pkcs5S2ParametersGenerator(new Sha256Digest()); - - //note Pkcs5PasswordToUtf8Bytes is the same as Encoding.UTF8.GetBytes(password) - //changing it to keep it as bouncy - - pdb.Init(PbeParametersGenerator.Pkcs5PasswordToUtf8Bytes(password.ToCharArray()), salt, - count); - //if dklen == 32, then it is 256 (8 * 32) - var key = (KeyParameter)pdb.GenerateDerivedMacParameters(8 * dklen); - return key.GetKey(); - } - - public byte[] GenerateAesCtrCipher(byte[] iv, byte[] encryptKey, byte[] input) - { - var key = ParameterUtilities.CreateKeyParameter("AES", encryptKey); - - var parametersWithIv = new ParametersWithIV(key, iv); - - var cipher = CipherUtilities.GetCipher("AES/CTR/NoPadding"); - cipher.Init(true, parametersWithIv); - return cipher.DoFinal(input); - } - - public byte[] DecryptScrypt(string password, byte[] mac, byte[] iv, byte[] cipherText, int n, int p, int r, - byte[] salt, int dklen) - { - var derivedKey = GenerateDerivedScryptKey(GetPasswordAsBytes(password), salt, n, r, p, dklen, false); - return Decrypt(mac, iv, cipherText, derivedKey); - } - - public byte[] DecryptPbkdf2Sha256(string password, byte[] mac, byte[] iv, byte[] cipherText, int c, byte[] salt, - int dklen) - { - var derivedKey = GeneratePbkdf2Sha256DerivedKey(password, salt, c, dklen); - return Decrypt(mac, iv, cipherText, derivedKey); - } - - public byte[] Decrypt(byte[] mac, byte[] iv, byte[] cipherText, byte[] derivedKey) - { - ValidateMac(mac, cipherText, derivedKey); - var encryptKey = new byte[16]; - Array.Copy(derivedKey, encryptKey, 16); - var privateKey = GenerateAesCtrCipher(iv, encryptKey, cipherText); - return privateKey; - } - - private void ValidateMac(byte[] mac, byte[] cipherText, byte[] derivedKey) - { - var generatedMac = GenerateMac(derivedKey, cipherText); - if (generatedMac.ToHex() != mac.ToHex()) - throw new DecryptionException( - "Cannot derive the same mac as the one provided from the cipher and derived key"); - } - - public byte[] GetPasswordAsBytes(string password) - { - return Encoding.UTF8.GetBytes(password); - } - } +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Crypto.Generators; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Security; +using Solnet.KeyStore.Exceptions; +using System; +using System.Text; + +namespace Solnet.KeyStore.Crypto +{ + //https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition + public class KeyStoreCrypto + { + public byte[] GenerateDerivedScryptKey(byte[] password, byte[] salt, int n, int r, int p, int dkLen, bool checkRandN = false) + { + if (checkRandN) + { + if (r == 1 && n >= 65536) + { + throw new ArgumentException("Cost parameter N must be > 1 and < 65536."); + } + } + + return Scrypt.CryptoScrypt(password, salt, n, r, p, dkLen); + } + + public byte[] GenerateCipherKey(byte[] derivedKey) + { + var cypherKey = new byte[16]; + Array.Copy(derivedKey, cypherKey, 16); + return cypherKey; + } + + public byte[] CalculateKeccakHash(byte[] value) + { + var digest = new KeccakDigest(256); + var output = new byte[digest.GetDigestSize()]; + digest.BlockUpdate(value, 0, value.Length); + digest.DoFinal(output, 0); + return output; + } + + public byte[] GenerateMac(byte[] derivedKey, byte[] cipherText) + { + var result = new byte[16 + cipherText.Length]; + Array.Copy(derivedKey, 16, result, 0, 16); + Array.Copy(cipherText, 0, result, 16, cipherText.Length); + return CalculateKeccakHash(result); + } + + public byte[] GeneratePbkdf2Sha256DerivedKey(string password, byte[] salt, int count, int dklen) + { + var pdb = new Pkcs5S2ParametersGenerator(new Sha256Digest()); + + //note Pkcs5PasswordToUtf8Bytes is the same as Encoding.UTF8.GetBytes(password) + //changing it to keep it as bouncy + + pdb.Init(PbeParametersGenerator.Pkcs5PasswordToUtf8Bytes(password.ToCharArray()), salt, + count); + //if dklen == 32, then it is 256 (8 * 32) + var key = (KeyParameter)pdb.GenerateDerivedMacParameters(8 * dklen); + return key.GetKey(); + } + + public byte[] GenerateAesCtrCipher(byte[] iv, byte[] encryptKey, byte[] input) + { + var key = ParameterUtilities.CreateKeyParameter("AES", encryptKey); + + var parametersWithIv = new ParametersWithIV(key, iv); + + var cipher = CipherUtilities.GetCipher("AES/CTR/NoPadding"); + cipher.Init(true, parametersWithIv); + return cipher.DoFinal(input); + } + + public byte[] DecryptScrypt(string password, byte[] mac, byte[] iv, byte[] cipherText, int n, int p, int r, + byte[] salt, int dklen) + { + var derivedKey = GenerateDerivedScryptKey(GetPasswordAsBytes(password), salt, n, r, p, dklen, false); + return Decrypt(mac, iv, cipherText, derivedKey); + } + + public byte[] DecryptPbkdf2Sha256(string password, byte[] mac, byte[] iv, byte[] cipherText, int c, byte[] salt, + int dklen) + { + var derivedKey = GeneratePbkdf2Sha256DerivedKey(password, salt, c, dklen); + return Decrypt(mac, iv, cipherText, derivedKey); + } + + public byte[] Decrypt(byte[] mac, byte[] iv, byte[] cipherText, byte[] derivedKey) + { + ValidateMac(mac, cipherText, derivedKey); + var encryptKey = new byte[16]; + Array.Copy(derivedKey, encryptKey, 16); + var privateKey = GenerateAesCtrCipher(iv, encryptKey, cipherText); + return privateKey; + } + + private void ValidateMac(byte[] mac, byte[] cipherText, byte[] derivedKey) + { + var generatedMac = GenerateMac(derivedKey, cipherText); + if (generatedMac.ToHex() != mac.ToHex()) + throw new DecryptionException( + "Cannot derive the same mac as the one provided from the cipher and derived key"); + } + + public byte[] GetPasswordAsBytes(string password) + { + return Encoding.UTF8.GetBytes(password); + } + } } \ No newline at end of file diff --git a/src/Solnet.KeyStore/Crypto/RandomBytesGenerator.cs b/src/Solnet.KeyStore/Crypto/RandomBytesGenerator.cs index 15e804f8..42be6cf0 100644 --- a/src/Solnet.KeyStore/Crypto/RandomBytesGenerator.cs +++ b/src/Solnet.KeyStore/Crypto/RandomBytesGenerator.cs @@ -1,26 +1,26 @@ -using Org.BouncyCastle.Security; - -namespace Solnet.KeyStore.Crypto -{ - public class RandomBytesGenerator : IRandomBytesGenerator - { - private static readonly SecureRandom Random = new SecureRandom(); - - public byte[] GenerateRandomInitializationVector() - { - return GenerateRandomBytes(16); - } - - public byte[] GenerateRandomSalt() - { - return GenerateRandomBytes(32); - } - - private static byte[] GenerateRandomBytes(int size) - { - var bytes = new byte[size]; - Random.NextBytes(bytes); - return bytes; - } - } +using Org.BouncyCastle.Security; + +namespace Solnet.KeyStore.Crypto +{ + public class RandomBytesGenerator : IRandomBytesGenerator + { + private static readonly SecureRandom Random = new SecureRandom(); + + public byte[] GenerateRandomInitializationVector() + { + return GenerateRandomBytes(16); + } + + public byte[] GenerateRandomSalt() + { + return GenerateRandomBytes(32); + } + + private static byte[] GenerateRandomBytes(int size) + { + var bytes = new byte[size]; + Random.NextBytes(bytes); + return bytes; + } + } } \ No newline at end of file diff --git a/src/Solnet.KeyStore/Crypto/Scrypt.cs b/src/Solnet.KeyStore/Crypto/Scrypt.cs index eddbd8a5..6e42825c 100644 --- a/src/Solnet.KeyStore/Crypto/Scrypt.cs +++ b/src/Solnet.KeyStore/Crypto/Scrypt.cs @@ -1,318 +1,318 @@ -using Org.BouncyCastle.Crypto; -using Org.BouncyCastle.Crypto.Digests; -using Org.BouncyCastle.Crypto.Generators; -using Org.BouncyCastle.Crypto.Parameters; - -namespace Solnet.KeyStore.Crypto -{ - public class Scrypt - { - private static byte[] SingleIterationPbkdf2(byte[] p, byte[] s, int dkLen) - { - PbeParametersGenerator pGen = new Pkcs5S2ParametersGenerator(new Sha256Digest()); - pGen.Init(p, s, 1); - KeyParameter key = (KeyParameter)pGen.GenerateDerivedMacParameters(dkLen * 8); - return key.GetKey(); - } - - public static unsafe byte[] CryptoScrypt(byte[] password, byte[] salt, int n, int r, int p, int dkLen) - { - var ba = new byte[128 * r * p + 63]; - var xYa = new byte[256 * r + 63]; - var va = new byte[128 * r * n + 63]; - var buf = new byte[32]; - - ba = SingleIterationPbkdf2(password, salt, p * 128 * r); - - fixed (byte* b = ba) - fixed (void* v = va) - fixed (void* xy = xYa) - { - /* 2: for i = 0 to p - 1 do */ - for (var i = 0; i < p; i++) - { - /* 3: B_i <-- MF(B_i, N) */ - SMix(&b[i * 128 * r], r, n, (uint*)v, (uint*)xy); - } - } - - /* 5: DK <-- PBKDF2(P, B, 1, dkLen) */ - return SingleIterationPbkdf2(password, ba, dkLen); - } - - - /// - /// Copies a specified number of bytes from a source pointer to a destination pointer. - /// - private static unsafe void BulkCopy(void* dst, void* src, int len) - { - var d = (byte*)dst; - var s = (byte*)src; - - while (len >= 8) - { - *(ulong*)d = *(ulong*)s; - d += 8; - s += 8; - len -= 8; - } - if (len >= 4) - { - *(uint*)d = *(uint*)s; - d += 4; - - s += 4; - len -= 4; - } - if (len >= 2) - { - *(ushort*)d = *(ushort*)s; - d += 2; - s += 2; - len -= 2; - } - if (len >= 1) - { - *d = *s; - } - } - - /// - /// Copies a specified number of bytes from a source pointer to a destination pointer. - /// - private static unsafe void BulkXor(void* dst, void* src, int len) - { - var d = (byte*)dst; - var s = (byte*)src; - - while (len >= 8) - { - *(ulong*)d ^= *(ulong*)s; - d += 8; - s += 8; - len -= 8; - } - if (len >= 4) - { - *(uint*)d ^= *(uint*)s; - d += 4; - s += 4; - len -= 4; - } - if (len >= 2) - { - *(ushort*)d ^= *(ushort*)s; - d += 2; - s += 2; - len -= 2; - } - if (len >= 1) - { - *d ^= *s; - } - } - - /// - /// Encode an integer to byte array on any alignment in little endian format. - /// - private static unsafe void Encode32(byte* p, uint x) - { - p[0] = (byte)(x & 0xff); - p[1] = (byte)((x >> 8) & 0xff); - p[2] = (byte)((x >> 16) & 0xff); - p[3] = (byte)((x >> 24) & 0xff); - } - - /// - /// Decode an integer from byte array on any alignment in little endian format. - /// - private static unsafe uint Decode32(byte* p) - { - return - (p[0] + - ((uint)(p[1]) << 8) + - ((uint)(p[2]) << 16) + - ((uint)(p[3]) << 24)); - } - - /// - /// Apply the salsa20/8 core to the provided block. - /// - private static unsafe void Salsa208(uint* b) - { - uint x0 = b[0]; - uint x1 = b[1]; - uint x2 = b[2]; - uint x3 = b[3]; - uint x4 = b[4]; - uint x5 = b[5]; - uint x6 = b[6]; - uint x7 = b[7]; - uint x8 = b[8]; - uint x9 = b[9]; - uint x10 = b[10]; - uint x11 = b[11]; - uint x12 = b[12]; - uint x13 = b[13]; - uint x14 = b[14]; - uint x15 = b[15]; - - for (var i = 0; i < 8; i += 2) - { - //((x0 + x12) << 7) | ((x0 + x12) >> (32 - 7)); - /* Operate on columns. */ - x4 ^= R(x0 + x12, 7); x8 ^= R(x4 + x0, 9); - x12 ^= R(x8 + x4, 13); x0 ^= R(x12 + x8, 18); - - x9 ^= R(x5 + x1, 7); x13 ^= R(x9 + x5, 9); - x1 ^= R(x13 + x9, 13); x5 ^= R(x1 + x13, 18); - - x14 ^= R(x10 + x6, 7); x2 ^= R(x14 + x10, 9); - x6 ^= R(x2 + x14, 13); x10 ^= R(x6 + x2, 18); - - x3 ^= R(x15 + x11, 7); x7 ^= R(x3 + x15, 9); - x11 ^= R(x7 + x3, 13); x15 ^= R(x11 + x7, 18); - - /* Operate on rows. */ - x1 ^= R(x0 + x3, 7); x2 ^= R(x1 + x0, 9); - x3 ^= R(x2 + x1, 13); x0 ^= R(x3 + x2, 18); - - x6 ^= R(x5 + x4, 7); x7 ^= R(x6 + x5, 9); - x4 ^= R(x7 + x6, 13); x5 ^= R(x4 + x7, 18); - - x11 ^= R(x10 + x9, 7); x8 ^= R(x11 + x10, 9); - x9 ^= R(x8 + x11, 13); x10 ^= R(x9 + x8, 18); - - x12 ^= R(x15 + x14, 7); x13 ^= R(x12 + x15, 9); - x14 ^= R(x13 + x12, 13); x15 ^= R(x14 + x13, 18); - } - - b[0] += x0; - b[1] += x1; - b[2] += x2; - b[3] += x3; - b[4] += x4; - b[5] += x5; - b[6] += x6; - b[7] += x7; - b[8] += x8; - b[9] += x9; - b[10] += x10; - b[11] += x11; - b[12] += x12; - b[13] += x13; - b[14] += x14; - b[15] += x15; - } - - /// - /// Utility method for Salsa208. - /// - private static unsafe uint R(uint a, int b) - { - return (a << b) | (a >> (32 - b)); - } - - /// - /// Compute Bout = BlockMix_{salsa20/8, r}(Bin). The input Bin must be 128r - /// bytes in length; the output Bout must also be the same size. - /// The temporary space X must be 64 bytes. - /// - private static unsafe void BlockMix(uint* bin, uint* bout, uint* x, int r) - { - /* 1: X <-- B_{2r - 1} */ - BulkCopy(x, &bin[(2 * r - 1) * 16], 64); - - /* 2: for i = 0 to 2r - 1 do */ - for (var i = 0; i < 2 * r; i += 2) - { - /* 3: X <-- H(X \xor B_i) */ - BulkXor(x, &bin[i * 16], 64); - Salsa208(x); - - /* 4: Y_i <-- X */ - /* 6: B' <-- (Y_0, Y_2 ... Y_{2r-2}, Y_1, Y_3 ... Y_{2r-1}) */ - BulkCopy(&bout[i * 8], x, 64); - - /* 3: X <-- H(X \xor B_i) */ - BulkXor(x, &bin[i * 16 + 16], 64); - Salsa208(x); - - /* 4: Y_i <-- X */ - /* 6: B' <-- (Y_0, Y_2 ... Y_{2r-2}, Y_1, Y_3 ... Y_{2r-1}) */ - BulkCopy(&bout[i * 8 + r * 16], x, 64); - } - } - - /// - /// Return the result of parsing B_{2r-1} as a little-endian integer. - /// - private static unsafe long Integerify(uint* b, int r) - { - var x = (uint*)(((byte*)b) + (2 * r - 1) * 64); - - return (((long)(x[1]) << 32) + x[0]); - } - - /// - /// Compute B = SMix_r(B, N). The input B must be 128r bytes in length; - /// the temporary storage V must be 128rN bytes in length; the temporary - /// storage XY must be 256r + 64 bytes in length. The value N must be a - /// power of 2 greater than 1. The arrays B, V, and XY must be aligned to a - /// multiple of 64 bytes. - /// - private static unsafe void SMix(byte* b, int r, int n, uint* v, uint* xy) - { - var x = xy; - var y = &xy[32 * r]; - var z = &xy[64 * r]; - - /* 1: X <-- B */ - for (var k = 0; k < 32 * r; k++) - { - x[k] = Decode32(&b[4 * k]); - } - - /* 2: for i = 0 to N - 1 do */ - for (var i = 0L; i < n; i += 2) - { - /* 3: V_i <-- X */ - BulkCopy(&v[i * (32 * r)], x, 128 * r); - - /* 4: X <-- H(X) */ - BlockMix(x, y, z, r); - - /* 3: V_i <-- X */ - BulkCopy(&v[(i + 1) * (32 * r)], y, 128 * r); - - /* 4: X <-- H(X) */ - BlockMix(y, x, z, r); - } - - /* 6: for i = 0 to N - 1 do */ - for (var i = 0; i < n; i += 2) - { - /* 7: j <-- Integerify(X) mod N */ - var j = Integerify(x, r) & (n - 1); - - /* 8: X <-- H(X \xor V_j) */ - BulkXor(x, &v[j * (32 * r)], 128 * r); - BlockMix(x, y, z, r); - - /* 7: j <-- Integerify(X) mod N */ - j = Integerify(y, r) & (n - 1); - - /* 8: X <-- H(X \xor V_j) */ - BulkXor(y, &v[j * (32 * r)], 128 * r); - BlockMix(y, x, z, r); - } - - /* 10: B' <-- X */ - for (var k = 0; k < 32 * r; k++) - { - Encode32(&b[4 * k], x[k]); - } - } - - } +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Crypto.Generators; +using Org.BouncyCastle.Crypto.Parameters; + +namespace Solnet.KeyStore.Crypto +{ + public class Scrypt + { + private static byte[] SingleIterationPbkdf2(byte[] p, byte[] s, int dkLen) + { + PbeParametersGenerator pGen = new Pkcs5S2ParametersGenerator(new Sha256Digest()); + pGen.Init(p, s, 1); + KeyParameter key = (KeyParameter)pGen.GenerateDerivedMacParameters(dkLen * 8); + return key.GetKey(); + } + + public static unsafe byte[] CryptoScrypt(byte[] password, byte[] salt, int n, int r, int p, int dkLen) + { + var ba = new byte[128 * r * p + 63]; + var xYa = new byte[256 * r + 63]; + var va = new byte[128 * r * n + 63]; + var buf = new byte[32]; + + ba = SingleIterationPbkdf2(password, salt, p * 128 * r); + + fixed (byte* b = ba) + fixed (void* v = va) + fixed (void* xy = xYa) + { + /* 2: for i = 0 to p - 1 do */ + for (var i = 0; i < p; i++) + { + /* 3: B_i <-- MF(B_i, N) */ + SMix(&b[i * 128 * r], r, n, (uint*)v, (uint*)xy); + } + } + + /* 5: DK <-- PBKDF2(P, B, 1, dkLen) */ + return SingleIterationPbkdf2(password, ba, dkLen); + } + + + /// + /// Copies a specified number of bytes from a source pointer to a destination pointer. + /// + private static unsafe void BulkCopy(void* dst, void* src, int len) + { + var d = (byte*)dst; + var s = (byte*)src; + + while (len >= 8) + { + *(ulong*)d = *(ulong*)s; + d += 8; + s += 8; + len -= 8; + } + if (len >= 4) + { + *(uint*)d = *(uint*)s; + d += 4; + + s += 4; + len -= 4; + } + if (len >= 2) + { + *(ushort*)d = *(ushort*)s; + d += 2; + s += 2; + len -= 2; + } + if (len >= 1) + { + *d = *s; + } + } + + /// + /// Copies a specified number of bytes from a source pointer to a destination pointer. + /// + private static unsafe void BulkXor(void* dst, void* src, int len) + { + var d = (byte*)dst; + var s = (byte*)src; + + while (len >= 8) + { + *(ulong*)d ^= *(ulong*)s; + d += 8; + s += 8; + len -= 8; + } + if (len >= 4) + { + *(uint*)d ^= *(uint*)s; + d += 4; + s += 4; + len -= 4; + } + if (len >= 2) + { + *(ushort*)d ^= *(ushort*)s; + d += 2; + s += 2; + len -= 2; + } + if (len >= 1) + { + *d ^= *s; + } + } + + /// + /// Encode an integer to byte array on any alignment in little endian format. + /// + private static unsafe void Encode32(byte* p, uint x) + { + p[0] = (byte)(x & 0xff); + p[1] = (byte)((x >> 8) & 0xff); + p[2] = (byte)((x >> 16) & 0xff); + p[3] = (byte)((x >> 24) & 0xff); + } + + /// + /// Decode an integer from byte array on any alignment in little endian format. + /// + private static unsafe uint Decode32(byte* p) + { + return + (p[0] + + ((uint)(p[1]) << 8) + + ((uint)(p[2]) << 16) + + ((uint)(p[3]) << 24)); + } + + /// + /// Apply the salsa20/8 core to the provided block. + /// + private static unsafe void Salsa208(uint* b) + { + uint x0 = b[0]; + uint x1 = b[1]; + uint x2 = b[2]; + uint x3 = b[3]; + uint x4 = b[4]; + uint x5 = b[5]; + uint x6 = b[6]; + uint x7 = b[7]; + uint x8 = b[8]; + uint x9 = b[9]; + uint x10 = b[10]; + uint x11 = b[11]; + uint x12 = b[12]; + uint x13 = b[13]; + uint x14 = b[14]; + uint x15 = b[15]; + + for (var i = 0; i < 8; i += 2) + { + //((x0 + x12) << 7) | ((x0 + x12) >> (32 - 7)); + /* Operate on columns. */ + x4 ^= R(x0 + x12, 7); x8 ^= R(x4 + x0, 9); + x12 ^= R(x8 + x4, 13); x0 ^= R(x12 + x8, 18); + + x9 ^= R(x5 + x1, 7); x13 ^= R(x9 + x5, 9); + x1 ^= R(x13 + x9, 13); x5 ^= R(x1 + x13, 18); + + x14 ^= R(x10 + x6, 7); x2 ^= R(x14 + x10, 9); + x6 ^= R(x2 + x14, 13); x10 ^= R(x6 + x2, 18); + + x3 ^= R(x15 + x11, 7); x7 ^= R(x3 + x15, 9); + x11 ^= R(x7 + x3, 13); x15 ^= R(x11 + x7, 18); + + /* Operate on rows. */ + x1 ^= R(x0 + x3, 7); x2 ^= R(x1 + x0, 9); + x3 ^= R(x2 + x1, 13); x0 ^= R(x3 + x2, 18); + + x6 ^= R(x5 + x4, 7); x7 ^= R(x6 + x5, 9); + x4 ^= R(x7 + x6, 13); x5 ^= R(x4 + x7, 18); + + x11 ^= R(x10 + x9, 7); x8 ^= R(x11 + x10, 9); + x9 ^= R(x8 + x11, 13); x10 ^= R(x9 + x8, 18); + + x12 ^= R(x15 + x14, 7); x13 ^= R(x12 + x15, 9); + x14 ^= R(x13 + x12, 13); x15 ^= R(x14 + x13, 18); + } + + b[0] += x0; + b[1] += x1; + b[2] += x2; + b[3] += x3; + b[4] += x4; + b[5] += x5; + b[6] += x6; + b[7] += x7; + b[8] += x8; + b[9] += x9; + b[10] += x10; + b[11] += x11; + b[12] += x12; + b[13] += x13; + b[14] += x14; + b[15] += x15; + } + + /// + /// Utility method for Salsa208. + /// + private static unsafe uint R(uint a, int b) + { + return (a << b) | (a >> (32 - b)); + } + + /// + /// Compute Bout = BlockMix_{salsa20/8, r}(Bin). The input Bin must be 128r + /// bytes in length; the output Bout must also be the same size. + /// The temporary space X must be 64 bytes. + /// + private static unsafe void BlockMix(uint* bin, uint* bout, uint* x, int r) + { + /* 1: X <-- B_{2r - 1} */ + BulkCopy(x, &bin[(2 * r - 1) * 16], 64); + + /* 2: for i = 0 to 2r - 1 do */ + for (var i = 0; i < 2 * r; i += 2) + { + /* 3: X <-- H(X \xor B_i) */ + BulkXor(x, &bin[i * 16], 64); + Salsa208(x); + + /* 4: Y_i <-- X */ + /* 6: B' <-- (Y_0, Y_2 ... Y_{2r-2}, Y_1, Y_3 ... Y_{2r-1}) */ + BulkCopy(&bout[i * 8], x, 64); + + /* 3: X <-- H(X \xor B_i) */ + BulkXor(x, &bin[i * 16 + 16], 64); + Salsa208(x); + + /* 4: Y_i <-- X */ + /* 6: B' <-- (Y_0, Y_2 ... Y_{2r-2}, Y_1, Y_3 ... Y_{2r-1}) */ + BulkCopy(&bout[i * 8 + r * 16], x, 64); + } + } + + /// + /// Return the result of parsing B_{2r-1} as a little-endian integer. + /// + private static unsafe long Integerify(uint* b, int r) + { + var x = (uint*)(((byte*)b) + (2 * r - 1) * 64); + + return (((long)(x[1]) << 32) + x[0]); + } + + /// + /// Compute B = SMix_r(B, N). The input B must be 128r bytes in length; + /// the temporary storage V must be 128rN bytes in length; the temporary + /// storage XY must be 256r + 64 bytes in length. The value N must be a + /// power of 2 greater than 1. The arrays B, V, and XY must be aligned to a + /// multiple of 64 bytes. + /// + private static unsafe void SMix(byte* b, int r, int n, uint* v, uint* xy) + { + var x = xy; + var y = &xy[32 * r]; + var z = &xy[64 * r]; + + /* 1: X <-- B */ + for (var k = 0; k < 32 * r; k++) + { + x[k] = Decode32(&b[4 * k]); + } + + /* 2: for i = 0 to N - 1 do */ + for (var i = 0L; i < n; i += 2) + { + /* 3: V_i <-- X */ + BulkCopy(&v[i * (32 * r)], x, 128 * r); + + /* 4: X <-- H(X) */ + BlockMix(x, y, z, r); + + /* 3: V_i <-- X */ + BulkCopy(&v[(i + 1) * (32 * r)], y, 128 * r); + + /* 4: X <-- H(X) */ + BlockMix(y, x, z, r); + } + + /* 6: for i = 0 to N - 1 do */ + for (var i = 0; i < n; i += 2) + { + /* 7: j <-- Integerify(X) mod N */ + var j = Integerify(x, r) & (n - 1); + + /* 8: X <-- H(X \xor V_j) */ + BulkXor(x, &v[j * (32 * r)], 128 * r); + BlockMix(x, y, z, r); + + /* 7: j <-- Integerify(X) mod N */ + j = Integerify(y, r) & (n - 1); + + /* 8: X <-- H(X \xor V_j) */ + BulkXor(y, &v[j * (32 * r)], 128 * r); + BlockMix(y, x, z, r); + } + + /* 10: B' <-- X */ + for (var k = 0; k < 32 * r; k++) + { + Encode32(&b[4 * k], x[k]); + } + } + + } } \ No newline at end of file diff --git a/src/Solnet.KeyStore/Exceptions/DecryptionException.cs b/src/Solnet.KeyStore/Exceptions/DecryptionException.cs index 772b5ae4..6c879a87 100644 --- a/src/Solnet.KeyStore/Exceptions/DecryptionException.cs +++ b/src/Solnet.KeyStore/Exceptions/DecryptionException.cs @@ -1,11 +1,11 @@ -using System; - -namespace Solnet.KeyStore.Exceptions -{ - public class DecryptionException : Exception - { - internal DecryptionException(string msg) : base(msg) - { - } - } +using System; + +namespace Solnet.KeyStore.Exceptions +{ + public class DecryptionException : Exception + { + internal DecryptionException(string msg) : base(msg) + { + } + } } \ No newline at end of file diff --git a/src/Solnet.KeyStore/Exceptions/InvalidKdfException.cs b/src/Solnet.KeyStore/Exceptions/InvalidKdfException.cs index 2cde6563..2898eadc 100644 --- a/src/Solnet.KeyStore/Exceptions/InvalidKdfException.cs +++ b/src/Solnet.KeyStore/Exceptions/InvalidKdfException.cs @@ -1,11 +1,11 @@ -using System; - -namespace Solnet.KeyStore.Exceptions -{ - public class InvalidKdfException : Exception - { - public InvalidKdfException(string kdf) : base("Invalid kdf:" + kdf) - { - } - } +using System; + +namespace Solnet.KeyStore.Exceptions +{ + public class InvalidKdfException : Exception + { + public InvalidKdfException(string kdf) : base("Invalid kdf:" + kdf) + { + } + } } \ No newline at end of file diff --git a/src/Solnet.KeyStore/KeyStoreKdfChecker.cs b/src/Solnet.KeyStore/KeyStoreKdfChecker.cs index 15130162..68bdfdac 100644 --- a/src/Solnet.KeyStore/KeyStoreKdfChecker.cs +++ b/src/Solnet.KeyStore/KeyStoreKdfChecker.cs @@ -1,58 +1,58 @@ -using Solnet.KeyStore.Exceptions; -using Solnet.KeyStore.Model; -using Solnet.KeyStore.Services; -using System; -using System.Runtime.Serialization; -using System.Text.Json; - -namespace Solnet.KeyStore -{ - /// - /// Implements a checker for the 's . - /// - public static class KeyStoreKdfChecker - { - /// - /// Get the kdf type string from the json document. - /// - /// The json document. - /// The kdf type string. - /// Throws exception when json property crypto or kdf couldn't be found - private static string GetKdfTypeFromJson(JsonDocument keyStoreDocument) - { - var cryptoObjExist = keyStoreDocument.RootElement.TryGetProperty("crypto", out var cryptoObj); - if (!cryptoObjExist) throw new JsonException("could not get crypto params object from json"); - - var kdfObjExist = cryptoObj.TryGetProperty("kdf", out var kdfObj); - if (!kdfObjExist) throw new JsonException("could not get kdf object from json"); - - return kdfObj.GetString(); - } - - /// - /// Gets the kdf type from the json keystore. - /// - /// The json keystore. - /// The kdf type. - /// Throws exception when json param is null. - /// Throws exception when file could not be processed to . - /// Throws exception when kdf json property is null. - /// Throws exception when the kdf json property has an invalid value. - public static KdfType GetKeyStoreKdfType(string json) - { - if (json == null) throw new ArgumentNullException(nameof(json)); - var keyStoreDocument = JsonSerializer.Deserialize(json); - if (keyStoreDocument == null) throw new SerializationException("could not process json"); - - var kdfString = GetKdfTypeFromJson(keyStoreDocument); - - if (kdfString == null) throw new JsonException("could not get kdf type from json"); - return kdfString switch - { - KeyStorePbkdf2Service.KdfType => KdfType.Pbkdf2, - KeyStoreScryptService.KdfType => KdfType.Scrypt, - _ => throw new InvalidKdfException(kdfString) - }; - } - } +using Solnet.KeyStore.Exceptions; +using Solnet.KeyStore.Model; +using Solnet.KeyStore.Services; +using System; +using System.Runtime.Serialization; +using System.Text.Json; + +namespace Solnet.KeyStore +{ + /// + /// Implements a checker for the 's . + /// + public static class KeyStoreKdfChecker + { + /// + /// Get the kdf type string from the json document. + /// + /// The json document. + /// The kdf type string. + /// Throws exception when json property crypto or kdf couldn't be found + private static string GetKdfTypeFromJson(JsonDocument keyStoreDocument) + { + var cryptoObjExist = keyStoreDocument.RootElement.TryGetProperty("crypto", out var cryptoObj); + if (!cryptoObjExist) throw new JsonException("could not get crypto params object from json"); + + var kdfObjExist = cryptoObj.TryGetProperty("kdf", out var kdfObj); + if (!kdfObjExist) throw new JsonException("could not get kdf object from json"); + + return kdfObj.GetString(); + } + + /// + /// Gets the kdf type from the json keystore. + /// + /// The json keystore. + /// The kdf type. + /// Throws exception when json param is null. + /// Throws exception when file could not be processed to . + /// Throws exception when kdf json property is null. + /// Throws exception when the kdf json property has an invalid value. + public static KdfType GetKeyStoreKdfType(string json) + { + if (json == null) throw new ArgumentNullException(nameof(json)); + var keyStoreDocument = JsonSerializer.Deserialize(json); + if (keyStoreDocument == null) throw new SerializationException("could not process json"); + + var kdfString = GetKdfTypeFromJson(keyStoreDocument); + + if (kdfString == null) throw new JsonException("could not get kdf type from json"); + return kdfString switch + { + KeyStorePbkdf2Service.KdfType => KdfType.Pbkdf2, + KeyStoreScryptService.KdfType => KdfType.Scrypt, + _ => throw new InvalidKdfException(kdfString) + }; + } + } } \ No newline at end of file diff --git a/src/Solnet.KeyStore/Model/CipherParams.cs b/src/Solnet.KeyStore/Model/CipherParams.cs index e8812655..99efde12 100644 --- a/src/Solnet.KeyStore/Model/CipherParams.cs +++ b/src/Solnet.KeyStore/Model/CipherParams.cs @@ -1,20 +1,20 @@ -using System.Text.Json.Serialization; - -namespace Solnet.KeyStore.Model -{ - public class CipherParams - { - public CipherParams() - { - } - - public CipherParams(byte[] iv) - { - Iv = iv.ToHex(); - } - - [JsonPropertyName("iv")] - // ReSharper disable once MemberCanBePrivate.Global - public string Iv { get; init; } - } +using System.Text.Json.Serialization; + +namespace Solnet.KeyStore.Model +{ + public class CipherParams + { + public CipherParams() + { + } + + public CipherParams(byte[] iv) + { + Iv = iv.ToHex(); + } + + [JsonPropertyName("iv")] + // ReSharper disable once MemberCanBePrivate.Global + public string Iv { get; init; } + } } \ No newline at end of file diff --git a/src/Solnet.KeyStore/Model/CryptoInfo.cs b/src/Solnet.KeyStore/Model/CryptoInfo.cs index 750349a4..f1c8231c 100644 --- a/src/Solnet.KeyStore/Model/CryptoInfo.cs +++ b/src/Solnet.KeyStore/Model/CryptoInfo.cs @@ -1,52 +1,52 @@ -using System.Text.Json.Serialization; - -namespace Solnet.KeyStore.Model -{ - public class CryptoInfo where TKdfParams : KdfParams - { - public CryptoInfo() - { - } - - public CryptoInfo(string cipher, byte[] cipherText, byte[] iv, byte[] mac, byte[] salt, TKdfParams kdfParams, - string kdfType) - { - Cipher = cipher; - CipherText = cipherText.ToHex(); - Mac = mac.ToHex(); - CipherParams = new CipherParams(iv); - Kdfparams = kdfParams; - Kdfparams.Salt = salt.ToHex(); - Kdf = kdfType; - } - - [JsonPropertyName("cipher")] - // ReSharper disable once MemberCanBePrivate.Global - // ReSharper disable once UnusedAutoPropertyAccessor.Global - public string Cipher { get; } - - [JsonPropertyName("ciphertext")] - // ReSharper disable once MemberCanBePrivate.Global - public string CipherText { get; init; } - - // ReSharper disable once StringLiteralTypo - [JsonPropertyName("cipherparams")] - // ReSharper disable once MemberCanBePrivate.Global - public CipherParams CipherParams { get; init; } - - [JsonPropertyName("kdf")] - // ReSharper disable once MemberCanBePrivate.Global - // ReSharper disable once UnusedAutoPropertyAccessor.Global - public string Kdf { get; } - - [JsonPropertyName("mac")] - // ReSharper disable once MemberCanBePrivate.Global - public string Mac { get; init; } - - // ReSharper disable once StringLiteralTypo - [JsonPropertyName("kdfparams")] - // ReSharper disable once IdentifierTypo - // ReSharper disable once MemberCanBePrivate.Global - public TKdfParams Kdfparams { get; init; } - } +using System.Text.Json.Serialization; + +namespace Solnet.KeyStore.Model +{ + public class CryptoInfo where TKdfParams : KdfParams + { + public CryptoInfo() + { + } + + public CryptoInfo(string cipher, byte[] cipherText, byte[] iv, byte[] mac, byte[] salt, TKdfParams kdfParams, + string kdfType) + { + Cipher = cipher; + CipherText = cipherText.ToHex(); + Mac = mac.ToHex(); + CipherParams = new CipherParams(iv); + Kdfparams = kdfParams; + Kdfparams.Salt = salt.ToHex(); + Kdf = kdfType; + } + + [JsonPropertyName("cipher")] + // ReSharper disable once MemberCanBePrivate.Global + // ReSharper disable once UnusedAutoPropertyAccessor.Global + public string Cipher { get; } + + [JsonPropertyName("ciphertext")] + // ReSharper disable once MemberCanBePrivate.Global + public string CipherText { get; init; } + + // ReSharper disable once StringLiteralTypo + [JsonPropertyName("cipherparams")] + // ReSharper disable once MemberCanBePrivate.Global + public CipherParams CipherParams { get; init; } + + [JsonPropertyName("kdf")] + // ReSharper disable once MemberCanBePrivate.Global + // ReSharper disable once UnusedAutoPropertyAccessor.Global + public string Kdf { get; } + + [JsonPropertyName("mac")] + // ReSharper disable once MemberCanBePrivate.Global + public string Mac { get; init; } + + // ReSharper disable once StringLiteralTypo + [JsonPropertyName("kdfparams")] + // ReSharper disable once IdentifierTypo + // ReSharper disable once MemberCanBePrivate.Global + public TKdfParams Kdfparams { get; init; } + } } \ No newline at end of file diff --git a/src/Solnet.KeyStore/Model/KdfParams.cs b/src/Solnet.KeyStore/Model/KdfParams.cs index c2d6bd6f..72a20bde 100644 --- a/src/Solnet.KeyStore/Model/KdfParams.cs +++ b/src/Solnet.KeyStore/Model/KdfParams.cs @@ -1,15 +1,15 @@ -using System.Text.Json.Serialization; - -namespace Solnet.KeyStore.Model -{ - public class KdfParams - { - // ReSharper disable once StringLiteralTypo - [JsonPropertyName("dklen")] - // ReSharper disable once IdentifierTypo - public int Dklen { get; init; } - - [JsonPropertyName("salt")] - public string Salt { get; set; } - } +using System.Text.Json.Serialization; + +namespace Solnet.KeyStore.Model +{ + public class KdfParams + { + // ReSharper disable once StringLiteralTypo + [JsonPropertyName("dklen")] + // ReSharper disable once IdentifierTypo + public int Dklen { get; init; } + + [JsonPropertyName("salt")] + public string Salt { get; set; } + } } \ No newline at end of file diff --git a/src/Solnet.KeyStore/Model/KdfType.cs b/src/Solnet.KeyStore/Model/KdfType.cs index deb95697..76172f5c 100644 --- a/src/Solnet.KeyStore/Model/KdfType.cs +++ b/src/Solnet.KeyStore/Model/KdfType.cs @@ -1,8 +1,8 @@ -namespace Solnet.KeyStore.Model -{ - public enum KdfType - { - Scrypt, - Pbkdf2 - } +namespace Solnet.KeyStore.Model +{ + public enum KdfType + { + Scrypt, + Pbkdf2 + } } \ No newline at end of file diff --git a/src/Solnet.KeyStore/Model/KeyStore.cs b/src/Solnet.KeyStore/Model/KeyStore.cs index 1c39368d..413c12f3 100644 --- a/src/Solnet.KeyStore/Model/KeyStore.cs +++ b/src/Solnet.KeyStore/Model/KeyStore.cs @@ -1,19 +1,19 @@ -using System.Text.Json.Serialization; - -namespace Solnet.KeyStore.Model -{ - public class KeyStore where TKdfParams : KdfParams - { - [JsonPropertyName("crypto")] - public CryptoInfo Crypto { get; set; } - - [JsonPropertyName("id")] - public string Id { get; set; } - - [JsonPropertyName("address")] - public string Address { get; set; } - - [JsonPropertyName("version")] - public int Version { get; set; } - } +using System.Text.Json.Serialization; + +namespace Solnet.KeyStore.Model +{ + public class KeyStore where TKdfParams : KdfParams + { + [JsonPropertyName("crypto")] + public CryptoInfo Crypto { get; set; } + + [JsonPropertyName("id")] + public string Id { get; set; } + + [JsonPropertyName("address")] + public string Address { get; set; } + + [JsonPropertyName("version")] + public int Version { get; set; } + } } \ No newline at end of file diff --git a/src/Solnet.KeyStore/Model/Pbkdf2Params.cs b/src/Solnet.KeyStore/Model/Pbkdf2Params.cs index e4a5dc9e..7fcea6d6 100644 --- a/src/Solnet.KeyStore/Model/Pbkdf2Params.cs +++ b/src/Solnet.KeyStore/Model/Pbkdf2Params.cs @@ -1,15 +1,15 @@ - -using System.Text.Json.Serialization; - -namespace Solnet.KeyStore.Model -{ - public class Pbkdf2Params : KdfParams - { - [JsonPropertyName("c")] - public int Count { get; init; } - - [JsonPropertyName("prf")] - // ReSharper disable once UnusedAutoPropertyAccessor.Global - public string Prf { get; init; } - } + +using System.Text.Json.Serialization; + +namespace Solnet.KeyStore.Model +{ + public class Pbkdf2Params : KdfParams + { + [JsonPropertyName("c")] + public int Count { get; init; } + + [JsonPropertyName("prf")] + // ReSharper disable once UnusedAutoPropertyAccessor.Global + public string Prf { get; init; } + } } \ No newline at end of file diff --git a/src/Solnet.KeyStore/Model/ScryptParams.cs b/src/Solnet.KeyStore/Model/ScryptParams.cs index f2b58eee..f037778f 100644 --- a/src/Solnet.KeyStore/Model/ScryptParams.cs +++ b/src/Solnet.KeyStore/Model/ScryptParams.cs @@ -1,16 +1,16 @@ -using System.Text.Json.Serialization; - -namespace Solnet.KeyStore.Model -{ - public class ScryptParams : KdfParams - { - [JsonPropertyName("n")] - public int N { get; init; } - - [JsonPropertyName("r")] - public int R { get; init; } - - [JsonPropertyName("p")] - public int P { get; init; } - } +using System.Text.Json.Serialization; + +namespace Solnet.KeyStore.Model +{ + public class ScryptParams : KdfParams + { + [JsonPropertyName("n")] + public int N { get; init; } + + [JsonPropertyName("r")] + public int R { get; init; } + + [JsonPropertyName("p")] + public int P { get; init; } + } } \ No newline at end of file diff --git a/src/Solnet.KeyStore/SecretKeyStoreService.cs b/src/Solnet.KeyStore/SecretKeyStoreService.cs index b446e61a..82be9b8d 100644 --- a/src/Solnet.KeyStore/SecretKeyStoreService.cs +++ b/src/Solnet.KeyStore/SecretKeyStoreService.cs @@ -1,81 +1,81 @@ -using Solnet.KeyStore.Model; -using Solnet.KeyStore.Services; -using System; -using System.IO; -using System.Runtime.Serialization; -using System.Text.Json; -using JsonSerializer = System.Text.Json.JsonSerializer; - -namespace Solnet.KeyStore -{ - /// - /// Implements a keystore compatible with the web3 secret storage standard. - /// - public class SecretKeyStoreService - { - private readonly KeyStoreScryptService _keyStoreScryptService; - private readonly KeyStorePbkdf2Service _keyStorePbkdf2Service; - - public SecretKeyStoreService() - { - _keyStorePbkdf2Service = new KeyStorePbkdf2Service(); - _keyStoreScryptService = new KeyStoreScryptService(); - } - - public SecretKeyStoreService(KeyStoreScryptService keyStoreScryptService, KeyStorePbkdf2Service keyStorePbkdf2Service) - { - _keyStoreScryptService = keyStoreScryptService; - _keyStorePbkdf2Service = keyStorePbkdf2Service; - } - - public static string GetAddressFromKeyStore(string json) - { - if (json == null) throw new ArgumentNullException(nameof(json)); - var keyStoreDocument = JsonSerializer.Deserialize(json); - if (keyStoreDocument == null) throw new SerializationException("could not process json"); - - var addrExist = keyStoreDocument.RootElement.TryGetProperty("address", out var address); - if (!addrExist) throw new JsonException("could not get address from json"); - - return address.GetString(); - } - - public static string GenerateUtcFileName(string address) - { - if (address == null) throw new ArgumentNullException(nameof(address)); - return "utc--" + DateTime.UtcNow.ToString("O").Replace(":", "-") + "--" + address; - } - - public byte[] DecryptKeyStoreFromFile(string password, string filePath) - { - if (password == null) throw new ArgumentNullException(nameof(password)); - if (filePath == null) throw new ArgumentNullException(nameof(filePath)); - - using var file = File.OpenText(filePath); - var json = file.ReadToEnd(); - return DecryptKeyStoreFromJson(password, json); - } - - public byte[] DecryptKeyStoreFromJson(string password, string json) - { - if (password == null) throw new ArgumentNullException(nameof(password)); - if (json == null) throw new ArgumentNullException(nameof(json)); - - var type = KeyStoreKdfChecker.GetKeyStoreKdfType(json); - return type switch - { - KdfType.Pbkdf2 => _keyStorePbkdf2Service.DecryptKeyStoreFromJson(password, json), - KdfType.Scrypt => _keyStoreScryptService.DecryptKeyStoreFromJson(password, json), - _ => throw new Exception("Invalid kdf type") - }; - } - - public string EncryptAndGenerateDefaultKeyStoreAsJson(string password, byte[] key, string address) - { - if (password == null) throw new ArgumentNullException(nameof(password)); - if (address == null) throw new ArgumentNullException(nameof(address)); - - return _keyStoreScryptService.EncryptAndGenerateKeyStoreAsJson(password, key, address); - } - } +using Solnet.KeyStore.Model; +using Solnet.KeyStore.Services; +using System; +using System.IO; +using System.Runtime.Serialization; +using System.Text.Json; +using JsonSerializer = System.Text.Json.JsonSerializer; + +namespace Solnet.KeyStore +{ + /// + /// Implements a keystore compatible with the web3 secret storage standard. + /// + public class SecretKeyStoreService + { + private readonly KeyStoreScryptService _keyStoreScryptService; + private readonly KeyStorePbkdf2Service _keyStorePbkdf2Service; + + public SecretKeyStoreService() + { + _keyStorePbkdf2Service = new KeyStorePbkdf2Service(); + _keyStoreScryptService = new KeyStoreScryptService(); + } + + public SecretKeyStoreService(KeyStoreScryptService keyStoreScryptService, KeyStorePbkdf2Service keyStorePbkdf2Service) + { + _keyStoreScryptService = keyStoreScryptService; + _keyStorePbkdf2Service = keyStorePbkdf2Service; + } + + public static string GetAddressFromKeyStore(string json) + { + if (json == null) throw new ArgumentNullException(nameof(json)); + var keyStoreDocument = JsonSerializer.Deserialize(json); + if (keyStoreDocument == null) throw new SerializationException("could not process json"); + + var addrExist = keyStoreDocument.RootElement.TryGetProperty("address", out var address); + if (!addrExist) throw new JsonException("could not get address from json"); + + return address.GetString(); + } + + public static string GenerateUtcFileName(string address) + { + if (address == null) throw new ArgumentNullException(nameof(address)); + return "utc--" + DateTime.UtcNow.ToString("O").Replace(":", "-") + "--" + address; + } + + public byte[] DecryptKeyStoreFromFile(string password, string filePath) + { + if (password == null) throw new ArgumentNullException(nameof(password)); + if (filePath == null) throw new ArgumentNullException(nameof(filePath)); + + using var file = File.OpenText(filePath); + var json = file.ReadToEnd(); + return DecryptKeyStoreFromJson(password, json); + } + + public byte[] DecryptKeyStoreFromJson(string password, string json) + { + if (password == null) throw new ArgumentNullException(nameof(password)); + if (json == null) throw new ArgumentNullException(nameof(json)); + + var type = KeyStoreKdfChecker.GetKeyStoreKdfType(json); + return type switch + { + KdfType.Pbkdf2 => _keyStorePbkdf2Service.DecryptKeyStoreFromJson(password, json), + KdfType.Scrypt => _keyStoreScryptService.DecryptKeyStoreFromJson(password, json), + _ => throw new Exception("Invalid kdf type") + }; + } + + public string EncryptAndGenerateDefaultKeyStoreAsJson(string password, byte[] key, string address) + { + if (password == null) throw new ArgumentNullException(nameof(password)); + if (address == null) throw new ArgumentNullException(nameof(address)); + + return _keyStoreScryptService.EncryptAndGenerateKeyStoreAsJson(password, key, address); + } + } } \ No newline at end of file diff --git a/src/Solnet.KeyStore/Serialization/JsonKeyStorePbkdf2Serializer.cs b/src/Solnet.KeyStore/Serialization/JsonKeyStorePbkdf2Serializer.cs index 76a44d38..229ee3d6 100644 --- a/src/Solnet.KeyStore/Serialization/JsonKeyStorePbkdf2Serializer.cs +++ b/src/Solnet.KeyStore/Serialization/JsonKeyStorePbkdf2Serializer.cs @@ -1,17 +1,17 @@ -using Solnet.KeyStore.Model; - -namespace Solnet.KeyStore.Serialization -{ - public static class JsonKeyStorePbkdf2Serializer - { - public static string SerialisePbkdf2(KeyStore pbkdf2KeyStore) - { - return System.Text.Json.JsonSerializer.Serialize(pbkdf2KeyStore); - } - - public static KeyStore DeserializePbkdf2(string json) - { - return System.Text.Json.JsonSerializer.Deserialize>(json); - } - } +using Solnet.KeyStore.Model; + +namespace Solnet.KeyStore.Serialization +{ + public static class JsonKeyStorePbkdf2Serializer + { + public static string SerialisePbkdf2(KeyStore pbkdf2KeyStore) + { + return System.Text.Json.JsonSerializer.Serialize(pbkdf2KeyStore); + } + + public static KeyStore DeserializePbkdf2(string json) + { + return System.Text.Json.JsonSerializer.Deserialize>(json); + } + } } \ No newline at end of file diff --git a/src/Solnet.KeyStore/Serialization/JsonKeyStoreScryptSerializer.cs b/src/Solnet.KeyStore/Serialization/JsonKeyStoreScryptSerializer.cs index 490618f5..68f59999 100644 --- a/src/Solnet.KeyStore/Serialization/JsonKeyStoreScryptSerializer.cs +++ b/src/Solnet.KeyStore/Serialization/JsonKeyStoreScryptSerializer.cs @@ -1,17 +1,17 @@ -using Solnet.KeyStore.Model; - -namespace Solnet.KeyStore.Serialization -{ - public static class JsonKeyStoreScryptSerializer - { - public static string SerializeScrypt(KeyStore scryptKeyStore) - { - return System.Text.Json.JsonSerializer.Serialize(scryptKeyStore); - } - - public static KeyStore DeserializeScrypt(string json) - { - return System.Text.Json.JsonSerializer.Deserialize>(json); - } - } +using Solnet.KeyStore.Model; + +namespace Solnet.KeyStore.Serialization +{ + public static class JsonKeyStoreScryptSerializer + { + public static string SerializeScrypt(KeyStore scryptKeyStore) + { + return System.Text.Json.JsonSerializer.Serialize(scryptKeyStore); + } + + public static KeyStore DeserializeScrypt(string json) + { + return System.Text.Json.JsonSerializer.Deserialize>(json); + } + } } \ No newline at end of file diff --git a/src/Solnet.KeyStore/Services/ISecretKeyStore.cs b/src/Solnet.KeyStore/Services/ISecretKeyStore.cs index 5a6bbab1..81d57b41 100644 --- a/src/Solnet.KeyStore/Services/ISecretKeyStore.cs +++ b/src/Solnet.KeyStore/Services/ISecretKeyStore.cs @@ -1,52 +1,52 @@ -using Solnet.KeyStore.Model; - -namespace Solnet.KeyStore.Services -{ - public interface ISecretKeyStoreService where T : KdfParams - { - /// - /// Decrypt the keystore. - /// - /// - /// - /// - byte[] DecryptKeyStore(string password, KeyStore keyStore); - - /// - /// Deserialize keystore from json. - /// - /// - /// - KeyStore DeserializeKeyStoreFromJson(string json); - - /// - /// Encrypt and generate the keystore. - /// - /// - /// - /// - /// - KeyStore EncryptAndGenerateKeyStore(string password, byte[] privateKey, string address); - - /// - /// Encrypt and generate the keystore as json. - /// - /// - /// - /// - /// - string EncryptAndGenerateKeyStoreAsJson(string password, byte[] privateKey, string address); - - /// - /// Get keystore cipher type. - /// - /// - string GetCipherType(); - - /// - /// Get keystore key derivation function. - /// - /// - string GetKdfType(); - } +using Solnet.KeyStore.Model; + +namespace Solnet.KeyStore.Services +{ + public interface ISecretKeyStoreService where T : KdfParams + { + /// + /// Decrypt the keystore. + /// + /// + /// + /// + byte[] DecryptKeyStore(string password, KeyStore keyStore); + + /// + /// Deserialize keystore from json. + /// + /// + /// + KeyStore DeserializeKeyStoreFromJson(string json); + + /// + /// Encrypt and generate the keystore. + /// + /// + /// + /// + /// + KeyStore EncryptAndGenerateKeyStore(string password, byte[] privateKey, string address); + + /// + /// Encrypt and generate the keystore as json. + /// + /// + /// + /// + /// + string EncryptAndGenerateKeyStoreAsJson(string password, byte[] privateKey, string address); + + /// + /// Get keystore cipher type. + /// + /// + string GetCipherType(); + + /// + /// Get keystore key derivation function. + /// + /// + string GetKdfType(); + } } \ No newline at end of file diff --git a/src/Solnet.KeyStore/Services/KeyStorePbkdf2Service.cs b/src/Solnet.KeyStore/Services/KeyStorePbkdf2Service.cs index 03eaac65..ba15b7c1 100644 --- a/src/Solnet.KeyStore/Services/KeyStorePbkdf2Service.cs +++ b/src/Solnet.KeyStore/Services/KeyStorePbkdf2Service.cs @@ -1,63 +1,63 @@ -using Solnet.KeyStore.Crypto; -using Solnet.KeyStore.Model; -using Solnet.KeyStore.Serialization; -using System; - -namespace Solnet.KeyStore.Services -{ - public class KeyStorePbkdf2Service : KeyStoreServiceBase - { - public const string KdfType = "pbkdf2"; - - public KeyStorePbkdf2Service() - { - } - - public KeyStorePbkdf2Service(IRandomBytesGenerator randomBytesGenerator, KeyStoreCrypto keyStoreCrypto) : base( - randomBytesGenerator, keyStoreCrypto) - { - } - - public KeyStorePbkdf2Service(IRandomBytesGenerator randomBytesGenerator) : base(randomBytesGenerator) - { - } - - protected override byte[] GenerateDerivedKey(string password, byte[] salt, Pbkdf2Params kdfParams) - { - return KeyStoreCrypto.GeneratePbkdf2Sha256DerivedKey(password, salt, kdfParams.Count, kdfParams.Dklen); - } - - protected override Pbkdf2Params GetDefaultParams() - { - return new() { Dklen = 32, Count = 262144, Prf = "hmac-sha256" }; - } - - public override KeyStore DeserializeKeyStoreFromJson(string json) - { - return JsonKeyStorePbkdf2Serializer.DeserializePbkdf2(json); - } - - public override string SerializeKeyStoreToJson(KeyStore keyStore) - { - return JsonKeyStorePbkdf2Serializer.SerialisePbkdf2(keyStore); - } - - public override byte[] DecryptKeyStore(string password, KeyStore keyStore) - { - if (password == null) throw new ArgumentNullException(nameof(password)); - if (keyStore == null) throw new ArgumentNullException(nameof(keyStore)); - - return KeyStoreCrypto.DecryptPbkdf2Sha256(password, keyStore.Crypto.Mac.HexToByteArray(), - keyStore.Crypto.CipherParams.Iv.HexToByteArray(), - keyStore.Crypto.CipherText.HexToByteArray(), - keyStore.Crypto.Kdfparams.Count, - keyStore.Crypto.Kdfparams.Salt.HexToByteArray(), - keyStore.Crypto.Kdfparams.Dklen); - } - - public override string GetKdfType() - { - return KdfType; - } - } +using Solnet.KeyStore.Crypto; +using Solnet.KeyStore.Model; +using Solnet.KeyStore.Serialization; +using System; + +namespace Solnet.KeyStore.Services +{ + public class KeyStorePbkdf2Service : KeyStoreServiceBase + { + public const string KdfType = "pbkdf2"; + + public KeyStorePbkdf2Service() + { + } + + public KeyStorePbkdf2Service(IRandomBytesGenerator randomBytesGenerator, KeyStoreCrypto keyStoreCrypto) : base( + randomBytesGenerator, keyStoreCrypto) + { + } + + public KeyStorePbkdf2Service(IRandomBytesGenerator randomBytesGenerator) : base(randomBytesGenerator) + { + } + + protected override byte[] GenerateDerivedKey(string password, byte[] salt, Pbkdf2Params kdfParams) + { + return KeyStoreCrypto.GeneratePbkdf2Sha256DerivedKey(password, salt, kdfParams.Count, kdfParams.Dklen); + } + + protected override Pbkdf2Params GetDefaultParams() + { + return new() { Dklen = 32, Count = 262144, Prf = "hmac-sha256" }; + } + + public override KeyStore DeserializeKeyStoreFromJson(string json) + { + return JsonKeyStorePbkdf2Serializer.DeserializePbkdf2(json); + } + + public override string SerializeKeyStoreToJson(KeyStore keyStore) + { + return JsonKeyStorePbkdf2Serializer.SerialisePbkdf2(keyStore); + } + + public override byte[] DecryptKeyStore(string password, KeyStore keyStore) + { + if (password == null) throw new ArgumentNullException(nameof(password)); + if (keyStore == null) throw new ArgumentNullException(nameof(keyStore)); + + return KeyStoreCrypto.DecryptPbkdf2Sha256(password, keyStore.Crypto.Mac.HexToByteArray(), + keyStore.Crypto.CipherParams.Iv.HexToByteArray(), + keyStore.Crypto.CipherText.HexToByteArray(), + keyStore.Crypto.Kdfparams.Count, + keyStore.Crypto.Kdfparams.Salt.HexToByteArray(), + keyStore.Crypto.Kdfparams.Dklen); + } + + public override string GetKdfType() + { + return KdfType; + } + } } \ No newline at end of file diff --git a/src/Solnet.KeyStore/Services/KeyStoreScryptService.cs b/src/Solnet.KeyStore/Services/KeyStoreScryptService.cs index a186a314..af65f60a 100644 --- a/src/Solnet.KeyStore/Services/KeyStoreScryptService.cs +++ b/src/Solnet.KeyStore/Services/KeyStoreScryptService.cs @@ -1,68 +1,68 @@ -using Solnet.KeyStore.Crypto; -using Solnet.KeyStore.Model; -using Solnet.KeyStore.Serialization; -using System; - -namespace Solnet.KeyStore.Services -{ - public class KeyStoreScryptService : KeyStoreServiceBase - { - public const string KdfType = "scrypt"; - - public KeyStoreScryptService() - { - } - - public KeyStoreScryptService(IRandomBytesGenerator randomBytesGenerator, KeyStoreCrypto keyStoreCrypto) : base( - randomBytesGenerator, keyStoreCrypto) - { - } - - public KeyStoreScryptService(IRandomBytesGenerator randomBytesGenerator) : base(randomBytesGenerator) - { - } - - protected override byte[] GenerateDerivedKey(string password, byte[] salt, ScryptParams kdfParams) - { - return KeyStoreCrypto.GenerateDerivedScryptKey(KeyStoreCrypto.GetPasswordAsBytes(password), salt, - kdfParams.N, kdfParams.R, - kdfParams.P, kdfParams.Dklen); - } - - protected override ScryptParams GetDefaultParams() - { - return new() { Dklen = 32, N = 262144, R = 1, P = 8 }; - } - - public override KeyStore DeserializeKeyStoreFromJson(string json) - { - var keystore = JsonKeyStoreScryptSerializer.DeserializeScrypt(json); - return keystore; - } - - public override string SerializeKeyStoreToJson(KeyStore keyStore) - { - return JsonKeyStoreScryptSerializer.SerializeScrypt(keyStore); - } - - public override byte[] DecryptKeyStore(string password, KeyStore keyStore) - { - if (password == null) throw new ArgumentNullException(nameof(password)); - if (keyStore == null) throw new ArgumentNullException(nameof(keyStore)); - - return KeyStoreCrypto.DecryptScrypt(password, keyStore.Crypto.Mac.HexToByteArray(), - keyStore.Crypto.CipherParams.Iv.HexToByteArray(), - keyStore.Crypto.CipherText.HexToByteArray(), - keyStore.Crypto.Kdfparams.N, - keyStore.Crypto.Kdfparams.P, - keyStore.Crypto.Kdfparams.R, - keyStore.Crypto.Kdfparams.Salt.HexToByteArray(), - keyStore.Crypto.Kdfparams.Dklen); - } - - public override string GetKdfType() - { - return KdfType; - } - } +using Solnet.KeyStore.Crypto; +using Solnet.KeyStore.Model; +using Solnet.KeyStore.Serialization; +using System; + +namespace Solnet.KeyStore.Services +{ + public class KeyStoreScryptService : KeyStoreServiceBase + { + public const string KdfType = "scrypt"; + + public KeyStoreScryptService() + { + } + + public KeyStoreScryptService(IRandomBytesGenerator randomBytesGenerator, KeyStoreCrypto keyStoreCrypto) : base( + randomBytesGenerator, keyStoreCrypto) + { + } + + public KeyStoreScryptService(IRandomBytesGenerator randomBytesGenerator) : base(randomBytesGenerator) + { + } + + protected override byte[] GenerateDerivedKey(string password, byte[] salt, ScryptParams kdfParams) + { + return KeyStoreCrypto.GenerateDerivedScryptKey(KeyStoreCrypto.GetPasswordAsBytes(password), salt, + kdfParams.N, kdfParams.R, + kdfParams.P, kdfParams.Dklen); + } + + protected override ScryptParams GetDefaultParams() + { + return new() { Dklen = 32, N = 262144, R = 1, P = 8 }; + } + + public override KeyStore DeserializeKeyStoreFromJson(string json) + { + var keystore = JsonKeyStoreScryptSerializer.DeserializeScrypt(json); + return keystore; + } + + public override string SerializeKeyStoreToJson(KeyStore keyStore) + { + return JsonKeyStoreScryptSerializer.SerializeScrypt(keyStore); + } + + public override byte[] DecryptKeyStore(string password, KeyStore keyStore) + { + if (password == null) throw new ArgumentNullException(nameof(password)); + if (keyStore == null) throw new ArgumentNullException(nameof(keyStore)); + + return KeyStoreCrypto.DecryptScrypt(password, keyStore.Crypto.Mac.HexToByteArray(), + keyStore.Crypto.CipherParams.Iv.HexToByteArray(), + keyStore.Crypto.CipherText.HexToByteArray(), + keyStore.Crypto.Kdfparams.N, + keyStore.Crypto.Kdfparams.P, + keyStore.Crypto.Kdfparams.R, + keyStore.Crypto.Kdfparams.Salt.HexToByteArray(), + keyStore.Crypto.Kdfparams.Dklen); + } + + public override string GetKdfType() + { + return KdfType; + } + } } \ No newline at end of file diff --git a/src/Solnet.KeyStore/Services/KeyStoreServiceBase.cs b/src/Solnet.KeyStore/Services/KeyStoreServiceBase.cs index 1dc8065a..b682edac 100644 --- a/src/Solnet.KeyStore/Services/KeyStoreServiceBase.cs +++ b/src/Solnet.KeyStore/Services/KeyStoreServiceBase.cs @@ -1,107 +1,107 @@ -using Solnet.KeyStore.Crypto; -using Solnet.KeyStore.Model; -using System; - -namespace Solnet.KeyStore.Services -{ - public abstract class KeyStoreServiceBase : ISecretKeyStoreService where T : KdfParams - { - public const int CurrentVersion = 3; - protected readonly KeyStoreCrypto KeyStoreCrypto; - protected readonly IRandomBytesGenerator RandomBytesGenerator; - - protected KeyStoreServiceBase() : this(new RandomBytesGenerator(), new KeyStoreCrypto()) - { - } - - protected KeyStoreServiceBase(IRandomBytesGenerator randomBytesGenerator, KeyStoreCrypto keyStoreCrypto) - { - RandomBytesGenerator = randomBytesGenerator; - KeyStoreCrypto = keyStoreCrypto; - } - - - protected KeyStoreServiceBase(IRandomBytesGenerator randomBytesGenerator) - { - RandomBytesGenerator = randomBytesGenerator; - KeyStoreCrypto = new KeyStoreCrypto(); - } - - public KeyStore EncryptAndGenerateKeyStore(string password, byte[] privateKey, string address) - { - var kdfParams = GetDefaultParams(); - return EncryptAndGenerateKeyStore(password, privateKey, address, kdfParams); - } - - public string EncryptAndGenerateKeyStoreAsJson(string password, byte[] privateKey, string address) - { - var keyStore = EncryptAndGenerateKeyStore(password, privateKey, address); - return SerializeKeyStoreToJson(keyStore); - } - - public abstract KeyStore DeserializeKeyStoreFromJson(string json); - public abstract string SerializeKeyStoreToJson(KeyStore keyStore); - - public abstract byte[] DecryptKeyStore(string password, KeyStore keyStore); - - public abstract string GetKdfType(); - - public virtual string GetCipherType() - { - return "aes-128-ctr"; - } - - public KeyStore EncryptAndGenerateKeyStore(string password, byte[] privateKey, string address, T kdfParams) - { - if (password == null) throw new ArgumentNullException(nameof(password)); - if (privateKey == null) throw new ArgumentNullException(nameof(privateKey)); - if (address == null) throw new ArgumentNullException(nameof(address)); - if (kdfParams == null) throw new ArgumentNullException(nameof(kdfParams)); - - var salt = RandomBytesGenerator.GenerateRandomSalt(); - - var derivedKey = GenerateDerivedKey(password, salt, kdfParams); - - var cipherKey = KeyStoreCrypto.GenerateCipherKey(derivedKey); - - var iv = RandomBytesGenerator.GenerateRandomInitializationVector(); - - var cipherText = GenerateCipher(privateKey, iv, cipherKey); - - var mac = KeyStoreCrypto.GenerateMac(derivedKey, cipherText); - - var cryptoInfo = new CryptoInfo(GetCipherType(), cipherText, iv, mac, salt, kdfParams, GetKdfType()); - - var keyStore = new KeyStore - { - Version = CurrentVersion, - Address = address, - Id = Guid.NewGuid().ToString(), - Crypto = cryptoInfo - }; - - return keyStore; - } - - public string EncryptAndGenerateKeyStoreAsJson(string password, byte[] privateKey, string address, T kdfParams) - { - var keyStore = EncryptAndGenerateKeyStore(password, privateKey, address, kdfParams); - return SerializeKeyStoreToJson(keyStore); - } - - public byte[] DecryptKeyStoreFromJson(string password, string json) - { - var keyStore = DeserializeKeyStoreFromJson(json); - return DecryptKeyStore(password, keyStore); - } - - protected virtual byte[] GenerateCipher(byte[] privateKey, byte[] iv, byte[] cipherKey) - { - return KeyStoreCrypto.GenerateAesCtrCipher(iv, cipherKey, privateKey); - } - - protected abstract byte[] GenerateDerivedKey(string password, byte[] salt, T kdfParams); - - protected abstract T GetDefaultParams(); - } +using Solnet.KeyStore.Crypto; +using Solnet.KeyStore.Model; +using System; + +namespace Solnet.KeyStore.Services +{ + public abstract class KeyStoreServiceBase : ISecretKeyStoreService where T : KdfParams + { + public const int CurrentVersion = 3; + protected readonly KeyStoreCrypto KeyStoreCrypto; + protected readonly IRandomBytesGenerator RandomBytesGenerator; + + protected KeyStoreServiceBase() : this(new RandomBytesGenerator(), new KeyStoreCrypto()) + { + } + + protected KeyStoreServiceBase(IRandomBytesGenerator randomBytesGenerator, KeyStoreCrypto keyStoreCrypto) + { + RandomBytesGenerator = randomBytesGenerator; + KeyStoreCrypto = keyStoreCrypto; + } + + + protected KeyStoreServiceBase(IRandomBytesGenerator randomBytesGenerator) + { + RandomBytesGenerator = randomBytesGenerator; + KeyStoreCrypto = new KeyStoreCrypto(); + } + + public KeyStore EncryptAndGenerateKeyStore(string password, byte[] privateKey, string address) + { + var kdfParams = GetDefaultParams(); + return EncryptAndGenerateKeyStore(password, privateKey, address, kdfParams); + } + + public string EncryptAndGenerateKeyStoreAsJson(string password, byte[] privateKey, string address) + { + var keyStore = EncryptAndGenerateKeyStore(password, privateKey, address); + return SerializeKeyStoreToJson(keyStore); + } + + public abstract KeyStore DeserializeKeyStoreFromJson(string json); + public abstract string SerializeKeyStoreToJson(KeyStore keyStore); + + public abstract byte[] DecryptKeyStore(string password, KeyStore keyStore); + + public abstract string GetKdfType(); + + public virtual string GetCipherType() + { + return "aes-128-ctr"; + } + + public KeyStore EncryptAndGenerateKeyStore(string password, byte[] privateKey, string address, T kdfParams) + { + if (password == null) throw new ArgumentNullException(nameof(password)); + if (privateKey == null) throw new ArgumentNullException(nameof(privateKey)); + if (address == null) throw new ArgumentNullException(nameof(address)); + if (kdfParams == null) throw new ArgumentNullException(nameof(kdfParams)); + + var salt = RandomBytesGenerator.GenerateRandomSalt(); + + var derivedKey = GenerateDerivedKey(password, salt, kdfParams); + + var cipherKey = KeyStoreCrypto.GenerateCipherKey(derivedKey); + + var iv = RandomBytesGenerator.GenerateRandomInitializationVector(); + + var cipherText = GenerateCipher(privateKey, iv, cipherKey); + + var mac = KeyStoreCrypto.GenerateMac(derivedKey, cipherText); + + var cryptoInfo = new CryptoInfo(GetCipherType(), cipherText, iv, mac, salt, kdfParams, GetKdfType()); + + var keyStore = new KeyStore + { + Version = CurrentVersion, + Address = address, + Id = Guid.NewGuid().ToString(), + Crypto = cryptoInfo + }; + + return keyStore; + } + + public string EncryptAndGenerateKeyStoreAsJson(string password, byte[] privateKey, string address, T kdfParams) + { + var keyStore = EncryptAndGenerateKeyStore(password, privateKey, address, kdfParams); + return SerializeKeyStoreToJson(keyStore); + } + + public byte[] DecryptKeyStoreFromJson(string password, string json) + { + var keyStore = DeserializeKeyStoreFromJson(json); + return DecryptKeyStore(password, keyStore); + } + + protected virtual byte[] GenerateCipher(byte[] privateKey, byte[] iv, byte[] cipherKey) + { + return KeyStoreCrypto.GenerateAesCtrCipher(iv, cipherKey, privateKey); + } + + protected abstract byte[] GenerateDerivedKey(string password, byte[] salt, T kdfParams); + + protected abstract T GetDefaultParams(); + } } \ No newline at end of file diff --git a/src/Solnet.KeyStore/SolanaKeyStore.cs b/src/Solnet.KeyStore/SolanaKeyStore.cs index 5fa6ec31..a01c390c 100644 --- a/src/Solnet.KeyStore/SolanaKeyStore.cs +++ b/src/Solnet.KeyStore/SolanaKeyStore.cs @@ -1,59 +1,59 @@ -using Solnet.Wallet; -using System.IO; -using System.Text; - -namespace Solnet.KeyStore -{ - /// - /// Implements a keystore compatible with the solana-keygen made in rust. - /// - public class SolanaKeyStoreService - { - - /// - /// Restores a keypair from a keystore compatible with the solana-keygen made in rust. - /// - /// The path to the keystore. - /// The passphrase used while originally generating the keys. - public Wallet.Wallet RestoreKeystore(string path, string passphrase = "") - { - var inputBytes = File.ReadAllText(path).FromStringByteArray(); - - var wallet = new Wallet.Wallet(inputBytes, passphrase, SeedMode.Bip39); - - return wallet; - } - - /// - /// NOT IMPLEMENTED. - /// - /// - /// - /// NOT IMPLEMENTED. - public Wallet.Wallet DecryptAndRestoreKeystore(string path) - { - throw new System.NotImplementedException(); - } - - /// - /// Saves a keypair to a keystore compatible with the solana-keygen made in rust. - /// - /// The path to the keystore - /// The wallet to save to the keystore. - public void SaveKeystore(string path, Wallet.Wallet wallet) - { - File.WriteAllBytes(path, Encoding.ASCII.GetBytes(wallet.DeriveMnemonicSeed().ToStringByteArray())); - } - - /// - /// NOT IMPLEMENTED. - /// - /// - /// The wallet to save to the keystore. - /// NOT IMPLEMENTED. - public void EncryptAndSaveKeystore(string path, Wallet.Wallet wallet) - { - throw new System.NotImplementedException(); - } - } +using Solnet.Wallet; +using System.IO; +using System.Text; + +namespace Solnet.KeyStore +{ + /// + /// Implements a keystore compatible with the solana-keygen made in rust. + /// + public class SolanaKeyStoreService + { + + /// + /// Restores a keypair from a keystore compatible with the solana-keygen made in rust. + /// + /// The path to the keystore. + /// The passphrase used while originally generating the keys. + public Wallet.Wallet RestoreKeystore(string path, string passphrase = "") + { + var inputBytes = File.ReadAllText(path).FromStringByteArray(); + + var wallet = new Wallet.Wallet(inputBytes, passphrase, SeedMode.Bip39); + + return wallet; + } + + /// + /// NOT IMPLEMENTED. + /// + /// + /// + /// NOT IMPLEMENTED. + public Wallet.Wallet DecryptAndRestoreKeystore(string path) + { + throw new System.NotImplementedException(); + } + + /// + /// Saves a keypair to a keystore compatible with the solana-keygen made in rust. + /// + /// The path to the keystore + /// The wallet to save to the keystore. + public void SaveKeystore(string path, Wallet.Wallet wallet) + { + File.WriteAllBytes(path, Encoding.ASCII.GetBytes(wallet.DeriveMnemonicSeed().ToStringByteArray())); + } + + /// + /// NOT IMPLEMENTED. + /// + /// + /// The wallet to save to the keystore. + /// NOT IMPLEMENTED. + public void EncryptAndSaveKeystore(string path, Wallet.Wallet wallet) + { + throw new System.NotImplementedException(); + } + } } \ No newline at end of file diff --git a/src/Solnet.KeyStore/Utils.cs b/src/Solnet.KeyStore/Utils.cs index 528a36fc..10753513 100644 --- a/src/Solnet.KeyStore/Utils.cs +++ b/src/Solnet.KeyStore/Utils.cs @@ -1,131 +1,131 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Solnet.KeyStore -{ - public static class Utils - { - - /// - /// Helper function to convert a character to byte. - /// - /// The character to convert - /// The value that represents the character's position. - /// Number of bits to shift. - /// The corresponding byte. - /// Throws format exception when the character is not a valid alphanumeric character. - private static byte FromCharacterToByte(char character, int index, int shift = 0) - { - var value = (byte)character; - if (0x40 < value && 0x47 > value || 0x60 < value && 0x67 > value) - { - if (0x40 == (0x40 & value)) - if (0x20 == (0x20 & value)) - value = (byte)((value + 0xA - 0x61) << shift); - else - value = (byte)((value + 0xA - 0x41) << shift); - } - else if (0x29 < value && 0x40 > value) - { - value = (byte)((value - 0x30) << shift); - } - else - { - throw new FormatException( - $"Character '{character}' at index '{index}' is not valid alphanumeric character."); - } - - return value; - } - - /// - /// Performs the hex to byte array conversion. - /// - /// The string to convert to byte array. - /// The corresponding byte array. - private static byte[] HexToByteArrayInternal(string value) - { - byte[] bytes = null; - if (string.IsNullOrEmpty(value)) - { - bytes = new byte[] { }; - } - else - { - var stringLength = value.Length; - var writeIndex = 0; - bytes = new byte[stringLength / 2]; // Initialize our byte array to hold the converted string. - - for (var readIndex = 0; readIndex < value.Length; readIndex += 2) - { - var upper = FromCharacterToByte(value[readIndex], readIndex, 4); - var lower = FromCharacterToByte(value[readIndex + 1], readIndex + 1); - - bytes[writeIndex++] = (byte)(upper | lower); - } - } - - return bytes; - } - - /// - /// Convert the passed string to a byte array. - /// - /// The string to convert - /// The equivalent byte array. - /// Throws format exception when the string could not be converted. - public static byte[] HexToByteArray(this string value) - { - try - { - return HexToByteArrayInternal(value); - } - catch (FormatException ex) - { - throw new FormatException($"String '{value}' could not be converted to byte array (not hex?).", ex); - } - } - - /// - /// Convert a byte array into a hexadecimal string. - /// - /// The byte array to convert. - /// The string as hex. - public static string ToHex(this byte[] value) - { - return string.Concat(value.Select(b => b.ToString("x2")).ToArray()); - } - - /// - /// Formats a byte array into a string in order to be compatible with the original solana-keygen made in rust. - /// - /// The byte array to be formatted. - /// A formatted string. - public static string ToStringByteArray(this IEnumerable bytes) => "[" + string.Join(",", bytes) + "]"; - - /// - /// Formats a string into a byte array in order to be compatible with the original solana-keygen made in rust. - /// - /// The string to be formatted. - /// A formatted byte array. - public static byte[] FromStringByteArray(this string data) - { - var bytes = new byte[64]; - var index = 0; - var i = 0; - var newS = data.AsSpan(1, data.Length - 1); - - while ((i = newS.IndexOf(',')) != -1) - { - bytes[index++] = byte.Parse(newS[..i]); - newS = newS[(i + 1)..]; - } - - bytes[index] = byte.Parse(newS[..^1]); - if (index != 63) - throw new ArgumentException("invalid string for conversion", nameof(data)); - return bytes; - } - } +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Solnet.KeyStore +{ + public static class Utils + { + + /// + /// Helper function to convert a character to byte. + /// + /// The character to convert + /// The value that represents the character's position. + /// Number of bits to shift. + /// The corresponding byte. + /// Throws format exception when the character is not a valid alphanumeric character. + private static byte FromCharacterToByte(char character, int index, int shift = 0) + { + var value = (byte)character; + if (0x40 < value && 0x47 > value || 0x60 < value && 0x67 > value) + { + if (0x40 == (0x40 & value)) + if (0x20 == (0x20 & value)) + value = (byte)((value + 0xA - 0x61) << shift); + else + value = (byte)((value + 0xA - 0x41) << shift); + } + else if (0x29 < value && 0x40 > value) + { + value = (byte)((value - 0x30) << shift); + } + else + { + throw new FormatException( + $"Character '{character}' at index '{index}' is not valid alphanumeric character."); + } + + return value; + } + + /// + /// Performs the hex to byte array conversion. + /// + /// The string to convert to byte array. + /// The corresponding byte array. + private static byte[] HexToByteArrayInternal(string value) + { + byte[] bytes = null; + if (string.IsNullOrEmpty(value)) + { + bytes = new byte[] { }; + } + else + { + var stringLength = value.Length; + var writeIndex = 0; + bytes = new byte[stringLength / 2]; // Initialize our byte array to hold the converted string. + + for (var readIndex = 0; readIndex < value.Length; readIndex += 2) + { + var upper = FromCharacterToByte(value[readIndex], readIndex, 4); + var lower = FromCharacterToByte(value[readIndex + 1], readIndex + 1); + + bytes[writeIndex++] = (byte)(upper | lower); + } + } + + return bytes; + } + + /// + /// Convert the passed string to a byte array. + /// + /// The string to convert + /// The equivalent byte array. + /// Throws format exception when the string could not be converted. + public static byte[] HexToByteArray(this string value) + { + try + { + return HexToByteArrayInternal(value); + } + catch (FormatException ex) + { + throw new FormatException($"String '{value}' could not be converted to byte array (not hex?).", ex); + } + } + + /// + /// Convert a byte array into a hexadecimal string. + /// + /// The byte array to convert. + /// The string as hex. + public static string ToHex(this byte[] value) + { + return string.Concat(value.Select(b => b.ToString("x2")).ToArray()); + } + + /// + /// Formats a byte array into a string in order to be compatible with the original solana-keygen made in rust. + /// + /// The byte array to be formatted. + /// A formatted string. + public static string ToStringByteArray(this IEnumerable bytes) => "[" + string.Join(",", bytes) + "]"; + + /// + /// Formats a string into a byte array in order to be compatible with the original solana-keygen made in rust. + /// + /// The string to be formatted. + /// A formatted byte array. + public static byte[] FromStringByteArray(this string data) + { + var bytes = new byte[64]; + var index = 0; + var i = 0; + var newS = data.AsSpan(1, data.Length - 1); + + while ((i = newS.IndexOf(',')) != -1) + { + bytes[index++] = byte.Parse(newS[..i]); + newS = newS[(i + 1)..]; + } + + bytes[index] = byte.Parse(newS[..^1]); + if (index != 63) + throw new ArgumentException("invalid string for conversion", nameof(data)); + return bytes; + } + } } \ No newline at end of file diff --git a/src/Solnet.Programs/MemoProgram.cs b/src/Solnet.Programs/MemoProgram.cs index bc304f4c..f85ee6a0 100644 --- a/src/Solnet.Programs/MemoProgram.cs +++ b/src/Solnet.Programs/MemoProgram.cs @@ -1,50 +1,50 @@ -using NBitcoin.DataEncoders; -using Solnet.Rpc.Models; -using Solnet.Wallet; -using System.Collections.Generic; -using System.Text; - -namespace Solnet.Programs -{ - /// - /// Helper class for the Memo Program. - /// - /// Used to write UTF-8 data into Solana transactions. - /// - /// - public static class MemoProgram - { - /// - /// The base58 encoder instance. - /// - private static readonly Base58Encoder Encoder = new(); - - /// - /// The address of the Memo Program. - /// - private static readonly string ProgramId = "Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFMNo"; - - /// - /// Initialize a new transaction instruction which interacts with the Memo Program. - /// - /// The account associated with the memo. - /// The memo to be included in the transaction. - /// The which includes the memo data. - public static TransactionInstruction NewMemo(Account account, string memo) - { - var keys = new List - { - new (account.PublicKey, true, false) - }; - var memoBytes = Encoding.UTF8.GetBytes(memo); - - return new TransactionInstruction - { - ProgramId = Encoder.DecodeData(ProgramId), - Keys = keys, - Data = memoBytes - }; - } - - } +using NBitcoin.DataEncoders; +using Solnet.Rpc.Models; +using Solnet.Wallet; +using System.Collections.Generic; +using System.Text; + +namespace Solnet.Programs +{ + /// + /// Helper class for the Memo Program. + /// + /// Used to write UTF-8 data into Solana transactions. + /// + /// + public static class MemoProgram + { + /// + /// The base58 encoder instance. + /// + private static readonly Base58Encoder Encoder = new(); + + /// + /// The address of the Memo Program. + /// + private static readonly string ProgramId = "Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFMNo"; + + /// + /// Initialize a new transaction instruction which interacts with the Memo Program. + /// + /// The account associated with the memo. + /// The memo to be included in the transaction. + /// The which includes the memo data. + public static TransactionInstruction NewMemo(Account account, string memo) + { + var keys = new List + { + new (account.PublicKey, true, false) + }; + var memoBytes = Encoding.UTF8.GetBytes(memo); + + return new TransactionInstruction + { + ProgramId = Encoder.DecodeData(ProgramId), + Keys = keys, + Data = memoBytes + }; + } + + } } \ No newline at end of file diff --git a/src/Solnet.Programs/SystemProgram.cs b/src/Solnet.Programs/SystemProgram.cs index 82a41986..259eee13 100644 --- a/src/Solnet.Programs/SystemProgram.cs +++ b/src/Solnet.Programs/SystemProgram.cs @@ -1,135 +1,135 @@ -using NBitcoin.DataEncoders; -using Solnet.Rpc.Models; -using System; -using System.Collections.Generic; - -namespace Solnet.Programs -{ - /// - /// Helper class for the System Program. - /// - /// Used to transfer lamports between accounts and create new accounts. - /// - /// - public static class SystemProgram - { - /// - /// The base58 encoder instance. - /// - private static readonly Base58Encoder Encoder = new(); - - /// - /// The address of the System Program. - /// - public static string ProgramId = "11111111111111111111111111111111"; - - /// - /// - /// - private static readonly int ProgramIndexCreateAccount = 0; - /// - /// - /// - private static readonly int ProgramIndexTransfer = 2; - - /// - /// Account layout size. - /// - public const int AccountDataSize = 165; - - /// - /// Initialize a transaction to transfer lamports. - /// - /// The account to transfer from. - /// The account to transfer to. - /// The amount of lamports - /// The transaction instruction. - public static TransactionInstruction Transfer(string fromPublicKey, string toPublicKey, long lamports) - { - return Transfer(Encoder.DecodeData(fromPublicKey), Encoder.DecodeData(toPublicKey), lamports); - } - - /// - /// Initialize a transaction to transfer lamports. - /// - /// The account to transfer from. - /// The account to transfer to. - /// The amount of lamports - /// The transaction instruction. - public static TransactionInstruction Transfer(byte[] fromPublicKey, byte[] toPublicKey, long lamports) - { - var keys = new List - { - new(fromPublicKey, true, true), - new(toPublicKey, false, true) - }; - var data = new byte[12]; - - Utils.Uint32ToByteArrayLe(ProgramIndexTransfer, data, 0); - Utils.Int64ToByteArrayLe(lamports, data, 4); - - return new TransactionInstruction - { - ProgramId = Encoder.DecodeData(ProgramId), - Keys = keys, - Data = data - }; - } - - /// - /// Initialize a new transaction instruction which interacts with the System Program to create a new account. - /// - /// The account from which the lamports will be transferred. - /// The account to which the lamports will be transferred. - /// The amount of lamports to transfer - /// Number of bytes of memory to allocate for the account. - /// - /// The transaction instruction. - public static TransactionInstruction CreateAccount( - string fromPublicKey, string newAccountPublicKey, long lamports, - long space, string programId) - { - return CreateAccount( - Encoder.DecodeData(fromPublicKey), - Encoder.DecodeData(newAccountPublicKey), - lamports, - space, - Encoder.DecodeData(programId) - ); - } - - /// - /// Initialize a new transaction instruction which interacts with the System Program to create a new account. - /// - /// The account from which the lamports will be transferred. - /// The account to which the lamports will be transferred. - /// The amount of lamports to transfer. - /// Number of bytes of memory to allocate for the account. - /// Public key of the program to assign as the owner of the created account. - /// The transaction instruction. - public static TransactionInstruction CreateAccount( - byte[] fromPublicKey, byte[] newAccountPublicKey, long lamports, - long space, byte[] programId) - { - var keys = new List - { - new(fromPublicKey, true, true), - new(newAccountPublicKey, true, true) - }; - var data = new byte[52]; - - Utils.Uint32ToByteArrayLe(ProgramIndexCreateAccount, data, 0); - Utils.Int64ToByteArrayLe(lamports, data, 4); - Utils.Int64ToByteArrayLe(space, data, 12); - Array.Copy(programId, 0, data, 20, 32); - - return new TransactionInstruction - { - ProgramId = Encoder.DecodeData(ProgramId), - Keys = keys, - Data = data - }; - } - - } +using NBitcoin.DataEncoders; +using Solnet.Rpc.Models; +using System; +using System.Collections.Generic; + +namespace Solnet.Programs +{ + /// + /// Helper class for the System Program. + /// + /// Used to transfer lamports between accounts and create new accounts. + /// + /// + public static class SystemProgram + { + /// + /// The base58 encoder instance. + /// + private static readonly Base58Encoder Encoder = new(); + + /// + /// The address of the System Program. + /// + public static string ProgramId = "11111111111111111111111111111111"; + + /// + /// + /// + private static readonly int ProgramIndexCreateAccount = 0; + /// + /// + /// + private static readonly int ProgramIndexTransfer = 2; + + /// + /// Account layout size. + /// + public const int AccountDataSize = 165; + + /// + /// Initialize a transaction to transfer lamports. + /// + /// The account to transfer from. + /// The account to transfer to. + /// The amount of lamports + /// The transaction instruction. + public static TransactionInstruction Transfer(string fromPublicKey, string toPublicKey, long lamports) + { + return Transfer(Encoder.DecodeData(fromPublicKey), Encoder.DecodeData(toPublicKey), lamports); + } + + /// + /// Initialize a transaction to transfer lamports. + /// + /// The account to transfer from. + /// The account to transfer to. + /// The amount of lamports + /// The transaction instruction. + public static TransactionInstruction Transfer(byte[] fromPublicKey, byte[] toPublicKey, long lamports) + { + var keys = new List + { + new(fromPublicKey, true, true), + new(toPublicKey, false, true) + }; + var data = new byte[12]; + + Utils.Uint32ToByteArrayLe(ProgramIndexTransfer, data, 0); + Utils.Int64ToByteArrayLe(lamports, data, 4); + + return new TransactionInstruction + { + ProgramId = Encoder.DecodeData(ProgramId), + Keys = keys, + Data = data + }; + } + + /// + /// Initialize a new transaction instruction which interacts with the System Program to create a new account. + /// + /// The account from which the lamports will be transferred. + /// The account to which the lamports will be transferred. + /// The amount of lamports to transfer + /// Number of bytes of memory to allocate for the account. + /// + /// The transaction instruction. + public static TransactionInstruction CreateAccount( + string fromPublicKey, string newAccountPublicKey, long lamports, + long space, string programId) + { + return CreateAccount( + Encoder.DecodeData(fromPublicKey), + Encoder.DecodeData(newAccountPublicKey), + lamports, + space, + Encoder.DecodeData(programId) + ); + } + + /// + /// Initialize a new transaction instruction which interacts with the System Program to create a new account. + /// + /// The account from which the lamports will be transferred. + /// The account to which the lamports will be transferred. + /// The amount of lamports to transfer. + /// Number of bytes of memory to allocate for the account. + /// Public key of the program to assign as the owner of the created account. + /// The transaction instruction. + public static TransactionInstruction CreateAccount( + byte[] fromPublicKey, byte[] newAccountPublicKey, long lamports, + long space, byte[] programId) + { + var keys = new List + { + new(fromPublicKey, true, true), + new(newAccountPublicKey, true, true) + }; + var data = new byte[52]; + + Utils.Uint32ToByteArrayLe(ProgramIndexCreateAccount, data, 0); + Utils.Int64ToByteArrayLe(lamports, data, 4); + Utils.Int64ToByteArrayLe(space, data, 12); + Array.Copy(programId, 0, data, 20, 32); + + return new TransactionInstruction + { + ProgramId = Encoder.DecodeData(ProgramId), + Keys = keys, + Data = data + }; + } + + } } \ No newline at end of file diff --git a/src/Solnet.Programs/TokenProgramInstructions.cs b/src/Solnet.Programs/TokenProgramInstructions.cs index d0354972..8a7d486b 100644 --- a/src/Solnet.Programs/TokenProgramInstructions.cs +++ b/src/Solnet.Programs/TokenProgramInstructions.cs @@ -1,92 +1,92 @@ -namespace Solnet.Programs -{ - /// - /// Represents the instruction types for the . - /// - public enum TokenProgramInstructions : byte - { - /// - /// Initialize a token mint. - /// - InitializeMint = 0, - - /// - /// Initialize a token account. - /// - InitializeAccount = 1, - - /// - /// Initialize a multi signature token account. - /// - InitializeMultiSignature = 2, - - /// - /// Transfer token transaction. - /// - Transfer = 3, - - /// - /// Approve token transaction. - /// - Approve = 4, - - /// - /// Revoke token transaction. - /// - Revoke = 5, - - /// - /// Set token authority transaction. - /// - SetAuthority = 6, - - /// - /// MintTo token account transaction. - /// - MintTo = 7, - - /// - /// Burn token transaction. - /// - Burn = 8, - - /// - /// Close token account transaction. - /// - CloseAccount = 9, - - /// - /// Freeze token account transaction. - /// - FreezeAccount = 10, - - /// - /// Thaw token account transaction. - /// - ThawAccount = 11, - - /// - /// Transfer checked token transaction. - /// Differs from in that the decimals value is asserted by the caller. - /// - TransferChecked = 12, - - /// - /// Approve checked token transaction. - /// Differs from in that the decimals value is asserted by the caller. - /// - ApproveChecked = 13, - - /// - /// MintTo checked token transaction. - /// Differs from in that the decimals value is asserted by the caller. - /// - MintToChecked = 14, - - /// - /// Burn checked token transaction. - /// Differs from in that the decimals value is asserted by the caller. - /// - BurnChecked = 15 - } +namespace Solnet.Programs +{ + /// + /// Represents the instruction types for the . + /// + public enum TokenProgramInstructions : byte + { + /// + /// Initialize a token mint. + /// + InitializeMint = 0, + + /// + /// Initialize a token account. + /// + InitializeAccount = 1, + + /// + /// Initialize a multi signature token account. + /// + InitializeMultiSignature = 2, + + /// + /// Transfer token transaction. + /// + Transfer = 3, + + /// + /// Approve token transaction. + /// + Approve = 4, + + /// + /// Revoke token transaction. + /// + Revoke = 5, + + /// + /// Set token authority transaction. + /// + SetAuthority = 6, + + /// + /// MintTo token account transaction. + /// + MintTo = 7, + + /// + /// Burn token transaction. + /// + Burn = 8, + + /// + /// Close token account transaction. + /// + CloseAccount = 9, + + /// + /// Freeze token account transaction. + /// + FreezeAccount = 10, + + /// + /// Thaw token account transaction. + /// + ThawAccount = 11, + + /// + /// Transfer checked token transaction. + /// Differs from in that the decimals value is asserted by the caller. + /// + TransferChecked = 12, + + /// + /// Approve checked token transaction. + /// Differs from in that the decimals value is asserted by the caller. + /// + ApproveChecked = 13, + + /// + /// MintTo checked token transaction. + /// Differs from in that the decimals value is asserted by the caller. + /// + MintToChecked = 14, + + /// + /// Burn checked token transaction. + /// Differs from in that the decimals value is asserted by the caller. + /// + BurnChecked = 15 + } } \ No newline at end of file diff --git a/src/Solnet.Programs/Utils.cs b/src/Solnet.Programs/Utils.cs index 03907298..0f0d5d64 100644 --- a/src/Solnet.Programs/Utils.cs +++ b/src/Solnet.Programs/Utils.cs @@ -1,40 +1,40 @@ -namespace Solnet.Programs -{ - /// - /// Utilities class for programs message encoding. - /// - public static class Utils - { - /// - /// Write 4 bytes to the byte array (starting at the offset) as unsigned 32-bit integer in little endian format. - /// - /// The value to write. - /// The array to write in. - /// The offset at which to start writing. - public static void Uint32ToByteArrayLe(long val, byte[] array, int offset) - { - array[offset] = (byte)(0xFF & val); - array[offset + 1] = (byte)(0xFF & (val >> 8)); - array[offset + 2] = (byte)(0xFF & (val >> 16)); - array[offset + 3] = (byte)(0xFF & (val >> 24)); - } - - /// - /// Write 8 bytes to the byte array (starting at the offset) as signed 64-bit integer in little endian format. - /// - /// The value to write. - /// The array to write in. - /// The offset at which to start writing. - public static void Int64ToByteArrayLe(long val, byte[] array, int offset) - { - array[offset] = (byte)(0xFF & val); - array[offset + 1] = (byte)(0xFF & (val >> 8)); - array[offset + 2] = (byte)(0xFF & (val >> 16)); - array[offset + 3] = (byte)(0xFF & (val >> 24)); - array[offset + 4] = (byte)(0xFF & (val >> 32)); - array[offset + 5] = (byte)(0xFF & (val >> 40)); - array[offset + 6] = (byte)(0xFF & (val >> 48)); - array[offset + 7] = (byte)(0xFF & (val >> 56)); - } - } +namespace Solnet.Programs +{ + /// + /// Utilities class for programs message encoding. + /// + public static class Utils + { + /// + /// Write 4 bytes to the byte array (starting at the offset) as unsigned 32-bit integer in little endian format. + /// + /// The value to write. + /// The array to write in. + /// The offset at which to start writing. + public static void Uint32ToByteArrayLe(long val, byte[] array, int offset) + { + array[offset] = (byte)(0xFF & val); + array[offset + 1] = (byte)(0xFF & (val >> 8)); + array[offset + 2] = (byte)(0xFF & (val >> 16)); + array[offset + 3] = (byte)(0xFF & (val >> 24)); + } + + /// + /// Write 8 bytes to the byte array (starting at the offset) as signed 64-bit integer in little endian format. + /// + /// The value to write. + /// The array to write in. + /// The offset at which to start writing. + public static void Int64ToByteArrayLe(long val, byte[] array, int offset) + { + array[offset] = (byte)(0xFF & val); + array[offset + 1] = (byte)(0xFF & (val >> 8)); + array[offset + 2] = (byte)(0xFF & (val >> 16)); + array[offset + 3] = (byte)(0xFF & (val >> 24)); + array[offset + 4] = (byte)(0xFF & (val >> 32)); + array[offset + 5] = (byte)(0xFF & (val >> 40)); + array[offset + 6] = (byte)(0xFF & (val >> 48)); + array[offset + 7] = (byte)(0xFF & (val >> 56)); + } + } } \ No newline at end of file diff --git a/src/Solnet.Rpc/Builders/MessageBuilder.cs b/src/Solnet.Rpc/Builders/MessageBuilder.cs index e367f9fe..7558afe5 100644 --- a/src/Solnet.Rpc/Builders/MessageBuilder.cs +++ b/src/Solnet.Rpc/Builders/MessageBuilder.cs @@ -1,260 +1,260 @@ -using NBitcoin.DataEncoders; -using Solnet.Rpc.Models; -using Solnet.Rpc.Utilities; -using Solnet.Wallet; -using System; -using System.Collections.Generic; -using System.IO; - -namespace Solnet.Rpc.Builders -{ - /// - /// The message builder. - /// - internal class MessageBuilder - { - /// - /// The base58 encoder instance. - /// - private static readonly Base58Encoder Encoder = new(); - - /// - /// The message header. - /// - private class MessageHeader - { - /// - /// The message header length. - /// - internal const int HeaderLength = 3; - - /// - /// The number of required signatures. - /// - internal byte RequiredSignatures { get; set; } - - /// - /// The number of read-only signed accounts. - /// - internal byte ReadOnlySignedAccounts { get; set; } - - /// - /// The number of read-only non-signed accounts. - /// - internal byte ReadOnlyUnsignedAccounts { get; set; } - - /// - /// Convert the message header to byte array format. - /// - /// The byte array. - internal byte[] ToBytes() - { - return new[] { RequiredSignatures, ReadOnlySignedAccounts, ReadOnlyUnsignedAccounts }; - } - } - - /// - /// A compiled instruction within the message. - /// - private class CompiledInstruction - { - internal byte ProgramIdIndex { get; init; } - - internal byte[] KeyIndicesCount { get; init; } - - internal byte[] KeyIndices { get; init; } - - internal byte[] DataLength { get; init; } - - internal byte[] Data { get; init; } - - /// - /// Get the length of the compiled instruction. - /// - /// The length. - internal int Length() - { - return 1 + KeyIndicesCount.Length + KeyIndices.Length + DataLength.Length + Data.Length; - } - } - - /// - /// The length of the block hash. - /// - private const int BlockHashLength = 32; - - /// - /// The message header. - /// - private MessageHeader _messageHeader; - - /// with read-write accounts first and read-only accounts following. - /// The account keys list. - /// - private readonly AccountKeysList _accountKeysList; - - /// - /// The list of instructions contained within this transaction. - /// - private readonly List _instructions; - - /// - /// The hash of a recent block. - /// - internal string RecentBlockHash { get; set; } - - /// - /// The transaction fee payer. - /// - internal Account FeePayer { get; set; } - - /// - /// Initialize the message builder. - /// - internal MessageBuilder() - { - _accountKeysList = new AccountKeysList(); - _instructions = new List(); - } - - /// - /// Add an instruction to the message. - /// - /// The instruction to add to the message. - /// The message builder, so instruction addition can be chained. - internal MessageBuilder AddInstruction(TransactionInstruction instruction) - { - _accountKeysList.Add(instruction.Keys); - _accountKeysList.Add(new AccountMeta(instruction.ProgramId, false, false)); - _instructions.Add(instruction); - - return this; - } - - /// - /// - /// - /// - internal byte[] Build() - { - if (RecentBlockHash == null) - // werent we supposed to fetch it from the api in this case? - throw new Exception("recent block hash is required"); - if (_instructions == null) - throw new Exception("no instructions provided in the transaction"); - - _messageHeader = new MessageHeader(); - - var keysList = GetAccountKeys(); - var accountAddressesLength = ShortVectorEncoding.EncodeLength(keysList.Count); - var compiledInstructionsLength = 0; - var compiledInstructions = new List(); - - foreach (var instruction in _instructions) - { - var keyCount = instruction.Keys.Count; - var keyIndices = new byte[keyCount]; - - for (var i = 0; i < keyCount; i++) - { - keyIndices[i] = (byte)FindAccountIndex(keysList, instruction.Keys[i].PublicKey); - } - - var compiledInstruction = new CompiledInstruction - { - ProgramIdIndex = (byte)FindAccountIndex(keysList, instruction.ProgramId), - KeyIndicesCount = ShortVectorEncoding.EncodeLength(keyCount), - KeyIndices = keyIndices, - DataLength = ShortVectorEncoding.EncodeLength(instruction.Data.Length), - Data = instruction.Data - }; - compiledInstructions.Add(compiledInstruction); - compiledInstructionsLength += compiledInstruction.Length(); - } - - - var accountKeysBufferSize = _accountKeysList.AccountList.Count * 32; - var accountKeysBuffer = new MemoryStream(accountKeysBufferSize); - var instructionsLength = ShortVectorEncoding.EncodeLength(compiledInstructions.Count); - - foreach (var accountMeta in keysList) - { - accountKeysBuffer.Write(accountMeta.PublicKey); - if (accountMeta.Signer) - { - _messageHeader.RequiredSignatures += 1; - if (!accountMeta.Writable) - _messageHeader.ReadOnlySignedAccounts += 1; - } - else - { - if (!accountMeta.Writable) - _messageHeader.ReadOnlyUnsignedAccounts += 1; - } - } - - #region Build Message Body - - - var messageBufferSize = MessageHeader.HeaderLength + BlockHashLength + accountAddressesLength.Length + - +instructionsLength.Length + compiledInstructionsLength + accountKeysBufferSize; - var buffer = new MemoryStream(messageBufferSize); - var messageHeaderBytes = _messageHeader.ToBytes(); - - buffer.Write(messageHeaderBytes); - buffer.Write(accountAddressesLength); - buffer.Write(accountKeysBuffer.ToArray()); - buffer.Write(Encoder.DecodeData(RecentBlockHash)); - buffer.Write(instructionsLength); - - foreach (var compiledInstruction in compiledInstructions) - { - buffer.WriteByte(compiledInstruction.ProgramIdIndex); - buffer.Write(compiledInstruction.KeyIndicesCount); - buffer.Write(compiledInstruction.KeyIndices); - buffer.Write(compiledInstruction.DataLength); - buffer.Write(compiledInstruction.Data); - } - - #endregion - - return buffer.ToArray(); - } - - /// - /// - /// - /// - private List GetAccountKeys() - { - var keysList = _accountKeysList.AccountList; - var feePayerIndex = FindAccountIndex(keysList, FeePayer.PublicKey); - - var newList = new List - { - new (keysList[feePayerIndex].PublicKey, true, true) - }; - keysList.RemoveAt(feePayerIndex); - newList.AddRange(keysList); - - return newList; - } - - /// - /// - /// - /// - /// - /// - /// - private int FindAccountIndex(IList accountMetas, byte[] publicKey) - { - for (var index = 0; index < accountMetas.Count; index++) - { - if (Encoder.EncodeData(accountMetas[index].PublicKey) == Encoder.EncodeData(publicKey)) return index; - } - - throw new Exception($"could not find account index for public key: {Encoder.EncodeData(publicKey)}"); - } - } +using NBitcoin.DataEncoders; +using Solnet.Rpc.Models; +using Solnet.Rpc.Utilities; +using Solnet.Wallet; +using System; +using System.Collections.Generic; +using System.IO; + +namespace Solnet.Rpc.Builders +{ + /// + /// The message builder. + /// + internal class MessageBuilder + { + /// + /// The base58 encoder instance. + /// + private static readonly Base58Encoder Encoder = new(); + + /// + /// The message header. + /// + private class MessageHeader + { + /// + /// The message header length. + /// + internal const int HeaderLength = 3; + + /// + /// The number of required signatures. + /// + internal byte RequiredSignatures { get; set; } + + /// + /// The number of read-only signed accounts. + /// + internal byte ReadOnlySignedAccounts { get; set; } + + /// + /// The number of read-only non-signed accounts. + /// + internal byte ReadOnlyUnsignedAccounts { get; set; } + + /// + /// Convert the message header to byte array format. + /// + /// The byte array. + internal byte[] ToBytes() + { + return new[] { RequiredSignatures, ReadOnlySignedAccounts, ReadOnlyUnsignedAccounts }; + } + } + + /// + /// A compiled instruction within the message. + /// + private class CompiledInstruction + { + internal byte ProgramIdIndex { get; init; } + + internal byte[] KeyIndicesCount { get; init; } + + internal byte[] KeyIndices { get; init; } + + internal byte[] DataLength { get; init; } + + internal byte[] Data { get; init; } + + /// + /// Get the length of the compiled instruction. + /// + /// The length. + internal int Length() + { + return 1 + KeyIndicesCount.Length + KeyIndices.Length + DataLength.Length + Data.Length; + } + } + + /// + /// The length of the block hash. + /// + private const int BlockHashLength = 32; + + /// + /// The message header. + /// + private MessageHeader _messageHeader; + + /// with read-write accounts first and read-only accounts following. + /// The account keys list. + /// + private readonly AccountKeysList _accountKeysList; + + /// + /// The list of instructions contained within this transaction. + /// + private readonly List _instructions; + + /// + /// The hash of a recent block. + /// + internal string RecentBlockHash { get; set; } + + /// + /// The transaction fee payer. + /// + internal Account FeePayer { get; set; } + + /// + /// Initialize the message builder. + /// + internal MessageBuilder() + { + _accountKeysList = new AccountKeysList(); + _instructions = new List(); + } + + /// + /// Add an instruction to the message. + /// + /// The instruction to add to the message. + /// The message builder, so instruction addition can be chained. + internal MessageBuilder AddInstruction(TransactionInstruction instruction) + { + _accountKeysList.Add(instruction.Keys); + _accountKeysList.Add(new AccountMeta(instruction.ProgramId, false, false)); + _instructions.Add(instruction); + + return this; + } + + /// + /// + /// + /// + internal byte[] Build() + { + if (RecentBlockHash == null) + // werent we supposed to fetch it from the api in this case? + throw new Exception("recent block hash is required"); + if (_instructions == null) + throw new Exception("no instructions provided in the transaction"); + + _messageHeader = new MessageHeader(); + + var keysList = GetAccountKeys(); + var accountAddressesLength = ShortVectorEncoding.EncodeLength(keysList.Count); + var compiledInstructionsLength = 0; + var compiledInstructions = new List(); + + foreach (var instruction in _instructions) + { + var keyCount = instruction.Keys.Count; + var keyIndices = new byte[keyCount]; + + for (var i = 0; i < keyCount; i++) + { + keyIndices[i] = (byte)FindAccountIndex(keysList, instruction.Keys[i].PublicKey); + } + + var compiledInstruction = new CompiledInstruction + { + ProgramIdIndex = (byte)FindAccountIndex(keysList, instruction.ProgramId), + KeyIndicesCount = ShortVectorEncoding.EncodeLength(keyCount), + KeyIndices = keyIndices, + DataLength = ShortVectorEncoding.EncodeLength(instruction.Data.Length), + Data = instruction.Data + }; + compiledInstructions.Add(compiledInstruction); + compiledInstructionsLength += compiledInstruction.Length(); + } + + + var accountKeysBufferSize = _accountKeysList.AccountList.Count * 32; + var accountKeysBuffer = new MemoryStream(accountKeysBufferSize); + var instructionsLength = ShortVectorEncoding.EncodeLength(compiledInstructions.Count); + + foreach (var accountMeta in keysList) + { + accountKeysBuffer.Write(accountMeta.PublicKey); + if (accountMeta.Signer) + { + _messageHeader.RequiredSignatures += 1; + if (!accountMeta.Writable) + _messageHeader.ReadOnlySignedAccounts += 1; + } + else + { + if (!accountMeta.Writable) + _messageHeader.ReadOnlyUnsignedAccounts += 1; + } + } + + #region Build Message Body + + + var messageBufferSize = MessageHeader.HeaderLength + BlockHashLength + accountAddressesLength.Length + + +instructionsLength.Length + compiledInstructionsLength + accountKeysBufferSize; + var buffer = new MemoryStream(messageBufferSize); + var messageHeaderBytes = _messageHeader.ToBytes(); + + buffer.Write(messageHeaderBytes); + buffer.Write(accountAddressesLength); + buffer.Write(accountKeysBuffer.ToArray()); + buffer.Write(Encoder.DecodeData(RecentBlockHash)); + buffer.Write(instructionsLength); + + foreach (var compiledInstruction in compiledInstructions) + { + buffer.WriteByte(compiledInstruction.ProgramIdIndex); + buffer.Write(compiledInstruction.KeyIndicesCount); + buffer.Write(compiledInstruction.KeyIndices); + buffer.Write(compiledInstruction.DataLength); + buffer.Write(compiledInstruction.Data); + } + + #endregion + + return buffer.ToArray(); + } + + /// + /// + /// + /// + private List GetAccountKeys() + { + var keysList = _accountKeysList.AccountList; + var feePayerIndex = FindAccountIndex(keysList, FeePayer.PublicKey); + + var newList = new List + { + new (keysList[feePayerIndex].PublicKey, true, true) + }; + keysList.RemoveAt(feePayerIndex); + newList.AddRange(keysList); + + return newList; + } + + /// + /// + /// + /// + /// + /// + /// + private int FindAccountIndex(IList accountMetas, byte[] publicKey) + { + for (var index = 0; index < accountMetas.Count; index++) + { + if (Encoder.EncodeData(accountMetas[index].PublicKey) == Encoder.EncodeData(publicKey)) return index; + } + + throw new Exception($"could not find account index for public key: {Encoder.EncodeData(publicKey)}"); + } + } } \ No newline at end of file diff --git a/src/Solnet.Rpc/Builders/TransactionBuilder.cs b/src/Solnet.Rpc/Builders/TransactionBuilder.cs index 1719e47d..f40cf99d 100644 --- a/src/Solnet.Rpc/Builders/TransactionBuilder.cs +++ b/src/Solnet.Rpc/Builders/TransactionBuilder.cs @@ -1,129 +1,129 @@ -using NBitcoin.DataEncoders; -using Solnet.Rpc.Models; -using Solnet.Rpc.Utilities; -using Solnet.Wallet; -using System; -using System.Collections.Generic; -using System.IO; - -namespace Solnet.Rpc.Builders -{ - /// - /// Implements a builder for transactions. - /// - public class TransactionBuilder - { - /// - /// The base58 encoder instance. - /// - private static readonly Base58Encoder Encoder = new(); - - /// - /// The length of a signature. - /// - private const int SignatureLength = 64; - - /// - /// The builder of the message contained within the transaction. - /// - private readonly MessageBuilder _messageBuilder; - - /// - /// The signatures present in the message. - /// - private readonly List _signatures; - - /// - /// The message after being serialized. - /// - private byte[] _serializedMessage; - - public TransactionBuilder() - { - _messageBuilder = new MessageBuilder(); - _signatures = new List(); - } - - - /// - /// Serializes the message into a byte array. - /// - private byte[] Serialize() - { - var signaturesLength = ShortVectorEncoding.EncodeLength(_signatures.Count); - var buffer = new MemoryStream(signaturesLength.Length + _signatures.Count * SignatureLength + _serializedMessage.Length); - - buffer.Write(signaturesLength); - foreach (var signature in _signatures) - { - buffer.Write(Encoder.DecodeData(signature)); - } - buffer.Write(_serializedMessage); - - return buffer.ToArray(); - } - - /// - /// Sign the transaction message with each of the signer's keys. - /// - /// The list of signers. - /// Throws exception when the list of signers is null or empty. - private void Sign(IList signers) - { - if (signers == null || signers.Count == 0) throw new Exception("no signers for the transaction"); - - _messageBuilder.FeePayer = signers[0]; - _serializedMessage = _messageBuilder.Build(); - - foreach (var signer in signers) - { - var signatureBytes = signer.Sign(_serializedMessage); - _signatures.Add(Encoder.EncodeData(signatureBytes)); - } - } - - /// - /// Sets the recent block hash for the transaction. - /// - /// - /// The transaction builder, so instruction addition can be chained. - public TransactionBuilder SetRecentBlockHash(string recentBlockHash) - { - _messageBuilder.RecentBlockHash = recentBlockHash; - return this; - } - - /// - /// Adds a new instruction to the transaction. - /// - /// The instruction to add. - /// The transaction builder, so instruction addition can be chained. - public TransactionBuilder AddInstruction(TransactionInstruction instruction) - { - _messageBuilder.AddInstruction(instruction); - return this; - } - - /// - /// Signs the transaction's message with the passed signer and add it to the transaction, serializing it. - /// - /// The signer. - /// The serialized transaction. - public byte[] Build(Account signer) - { - return Build(new List { signer }); - } - - /// - /// Signs the transaction's message with the passed list of signers and adds them to the transaction, serializing it. - /// - /// The list of signers. - /// The serialized transaction. - public byte[] Build(IList signers) - { - Sign(signers); - - return Serialize(); - } - } +using NBitcoin.DataEncoders; +using Solnet.Rpc.Models; +using Solnet.Rpc.Utilities; +using Solnet.Wallet; +using System; +using System.Collections.Generic; +using System.IO; + +namespace Solnet.Rpc.Builders +{ + /// + /// Implements a builder for transactions. + /// + public class TransactionBuilder + { + /// + /// The base58 encoder instance. + /// + private static readonly Base58Encoder Encoder = new(); + + /// + /// The length of a signature. + /// + private const int SignatureLength = 64; + + /// + /// The builder of the message contained within the transaction. + /// + private readonly MessageBuilder _messageBuilder; + + /// + /// The signatures present in the message. + /// + private readonly List _signatures; + + /// + /// The message after being serialized. + /// + private byte[] _serializedMessage; + + public TransactionBuilder() + { + _messageBuilder = new MessageBuilder(); + _signatures = new List(); + } + + + /// + /// Serializes the message into a byte array. + /// + private byte[] Serialize() + { + var signaturesLength = ShortVectorEncoding.EncodeLength(_signatures.Count); + var buffer = new MemoryStream(signaturesLength.Length + _signatures.Count * SignatureLength + _serializedMessage.Length); + + buffer.Write(signaturesLength); + foreach (var signature in _signatures) + { + buffer.Write(Encoder.DecodeData(signature)); + } + buffer.Write(_serializedMessage); + + return buffer.ToArray(); + } + + /// + /// Sign the transaction message with each of the signer's keys. + /// + /// The list of signers. + /// Throws exception when the list of signers is null or empty. + private void Sign(IList signers) + { + if (signers == null || signers.Count == 0) throw new Exception("no signers for the transaction"); + + _messageBuilder.FeePayer = signers[0]; + _serializedMessage = _messageBuilder.Build(); + + foreach (var signer in signers) + { + var signatureBytes = signer.Sign(_serializedMessage); + _signatures.Add(Encoder.EncodeData(signatureBytes)); + } + } + + /// + /// Sets the recent block hash for the transaction. + /// + /// + /// The transaction builder, so instruction addition can be chained. + public TransactionBuilder SetRecentBlockHash(string recentBlockHash) + { + _messageBuilder.RecentBlockHash = recentBlockHash; + return this; + } + + /// + /// Adds a new instruction to the transaction. + /// + /// The instruction to add. + /// The transaction builder, so instruction addition can be chained. + public TransactionBuilder AddInstruction(TransactionInstruction instruction) + { + _messageBuilder.AddInstruction(instruction); + return this; + } + + /// + /// Signs the transaction's message with the passed signer and add it to the transaction, serializing it. + /// + /// The signer. + /// The serialized transaction. + public byte[] Build(Account signer) + { + return Build(new List { signer }); + } + + /// + /// Signs the transaction's message with the passed list of signers and adds them to the transaction, serializing it. + /// + /// The list of signers. + /// The serialized transaction. + public byte[] Build(IList signers) + { + Sign(signers); + + return Serialize(); + } + } } \ No newline at end of file diff --git a/src/Solnet.Rpc/ClientFactory.cs b/src/Solnet.Rpc/ClientFactory.cs index f67f7f5d..10b0c895 100644 --- a/src/Solnet.Rpc/ClientFactory.cs +++ b/src/Solnet.Rpc/ClientFactory.cs @@ -1,127 +1,127 @@ -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Console; - -namespace Solnet.Rpc -{ - /// - /// Implements a client factory for Solana RPC and Streaming RPC APIs. - /// - public static class ClientFactory - { - /// - /// The dev net cluster. - /// - private const string RpcDevNet = "https://api.devnet.solana.com"; - - /// - /// The test net cluster. - /// - private const string RpcTestNet = "https://api.testnet.solana.com"; - - /// - /// The main net cluster. - /// - private const string RpcMainNet = "https://api.mainnet-beta.solana.com"; - - - /// - /// The dev net cluster. - /// - private const string StreamingRpcDevNet = "wss://devnet.solana.com"; - - /// - /// The test net cluster. - /// - private const string StreamingRpcTestNet = "wss://api.testnet.solana.com"; - - /// - /// The main net cluster. - /// - private const string StreamingRpcMainNet = "wss://api.mainnet-beta.solana.com"; - - /// - /// Instantiate a http client. - /// - /// The network cluster. - /// The logger. - /// The http client. - public static IRpcClient GetClient(Cluster cluster, ILogger logger = null) - { - var url = cluster switch - { - Cluster.DevNet => RpcDevNet, - Cluster.TestNet => RpcTestNet, - Cluster.MainNet => RpcMainNet, - }; - -#if DEBUG - logger = logger ?? LoggerFactory.Create(x => - { - x.AddSimpleConsole(o => - { - o.UseUtcTimestamp = true; - o.IncludeScopes = true; - o.ColorBehavior = LoggerColorBehavior.Enabled; - o.TimestampFormat = "HH:mm:ss "; - }) - .SetMinimumLevel(LogLevel.Debug); - }).CreateLogger(); -#endif - - return GetClient(url, logger); - } - - /// - /// Instantiate a http client. - /// - /// The network cluster url. - /// The logger. - /// The http client. - public static IRpcClient GetClient(string url, ILogger logger = null) - { - - return new SolanaRpcClient(url, logger); - } - - /// - /// Instantiate a streaming client. - /// - /// The network cluster. - /// The logger. - /// The streaming client. - public static IStreamingRpcClient GetStreamingClient(Cluster cluster, ILogger logger = null) - { - var url = cluster switch - { - Cluster.DevNet => StreamingRpcDevNet, - Cluster.TestNet => StreamingRpcTestNet, - Cluster.MainNet => StreamingRpcMainNet, - }; -#if DEBUG - logger = logger ?? LoggerFactory.Create(x => - { - x.AddSimpleConsole(o => - { - o.UseUtcTimestamp = true; - o.IncludeScopes = true; - o.ColorBehavior = LoggerColorBehavior.Enabled; - o.TimestampFormat = "HH:mm:ss "; - }) - .SetMinimumLevel(LogLevel.Debug); - }).CreateLogger(); -#endif - return GetStreamingClient(url, logger); - } - - /// - /// Instantiate a streaming client. - /// - /// The network cluster url. - /// The logger. - /// The streaming client. - public static IStreamingRpcClient GetStreamingClient(string url, ILogger logger = null) - { - return new SolanaStreamingRpcClient(url, logger); - } - } +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Console; + +namespace Solnet.Rpc +{ + /// + /// Implements a client factory for Solana RPC and Streaming RPC APIs. + /// + public static class ClientFactory + { + /// + /// The dev net cluster. + /// + private const string RpcDevNet = "https://api.devnet.solana.com"; + + /// + /// The test net cluster. + /// + private const string RpcTestNet = "https://api.testnet.solana.com"; + + /// + /// The main net cluster. + /// + private const string RpcMainNet = "https://api.mainnet-beta.solana.com"; + + + /// + /// The dev net cluster. + /// + private const string StreamingRpcDevNet = "wss://devnet.solana.com"; + + /// + /// The test net cluster. + /// + private const string StreamingRpcTestNet = "wss://api.testnet.solana.com"; + + /// + /// The main net cluster. + /// + private const string StreamingRpcMainNet = "wss://api.mainnet-beta.solana.com"; + + /// + /// Instantiate a http client. + /// + /// The network cluster. + /// The logger. + /// The http client. + public static IRpcClient GetClient(Cluster cluster, ILogger logger = null) + { + var url = cluster switch + { + Cluster.DevNet => RpcDevNet, + Cluster.TestNet => RpcTestNet, + Cluster.MainNet => RpcMainNet, + }; + +#if DEBUG + logger = logger ?? LoggerFactory.Create(x => + { + x.AddSimpleConsole(o => + { + o.UseUtcTimestamp = true; + o.IncludeScopes = true; + o.ColorBehavior = LoggerColorBehavior.Enabled; + o.TimestampFormat = "HH:mm:ss "; + }) + .SetMinimumLevel(LogLevel.Debug); + }).CreateLogger(); +#endif + + return GetClient(url, logger); + } + + /// + /// Instantiate a http client. + /// + /// The network cluster url. + /// The logger. + /// The http client. + public static IRpcClient GetClient(string url, ILogger logger = null) + { + + return new SolanaRpcClient(url, logger); + } + + /// + /// Instantiate a streaming client. + /// + /// The network cluster. + /// The logger. + /// The streaming client. + public static IStreamingRpcClient GetStreamingClient(Cluster cluster, ILogger logger = null) + { + var url = cluster switch + { + Cluster.DevNet => StreamingRpcDevNet, + Cluster.TestNet => StreamingRpcTestNet, + Cluster.MainNet => StreamingRpcMainNet, + }; +#if DEBUG + logger = logger ?? LoggerFactory.Create(x => + { + x.AddSimpleConsole(o => + { + o.UseUtcTimestamp = true; + o.IncludeScopes = true; + o.ColorBehavior = LoggerColorBehavior.Enabled; + o.TimestampFormat = "HH:mm:ss "; + }) + .SetMinimumLevel(LogLevel.Debug); + }).CreateLogger(); +#endif + return GetStreamingClient(url, logger); + } + + /// + /// Instantiate a streaming client. + /// + /// The network cluster url. + /// The logger. + /// The streaming client. + public static IStreamingRpcClient GetStreamingClient(string url, ILogger logger = null) + { + return new SolanaStreamingRpcClient(url, logger); + } + } } \ No newline at end of file diff --git a/src/Solnet.Rpc/Cluster.cs b/src/Solnet.Rpc/Cluster.cs index f17787f4..34210647 100644 --- a/src/Solnet.Rpc/Cluster.cs +++ b/src/Solnet.Rpc/Cluster.cs @@ -1,9 +1,9 @@ -namespace Solnet.Rpc -{ - public enum Cluster - { - DevNet, - TestNet, - MainNet - } +namespace Solnet.Rpc +{ + public enum Cluster + { + DevNet, + TestNet, + MainNet + } } \ No newline at end of file diff --git a/src/Solnet.Rpc/Core/Http/JsonRpcClient.cs b/src/Solnet.Rpc/Core/Http/JsonRpcClient.cs index ccad2efe..db29943e 100644 --- a/src/Solnet.Rpc/Core/Http/JsonRpcClient.cs +++ b/src/Solnet.Rpc/Core/Http/JsonRpcClient.cs @@ -1,144 +1,144 @@ -using Microsoft.Extensions.Logging; -using Solnet.Rpc.Messages; -using System; -using System.Net.Http; -using System.Text; -using System.Text.Json; -using System.Text.Json.Serialization; -using System.Threading.Tasks; - -namespace Solnet.Rpc.Core.Http -{ - /// - /// Base Rpc client class that abstracts the HttpClient handling. - /// - public abstract class JsonRpcClient - { - /// - /// The Json serializer options to be reused between calls. - /// - private readonly JsonSerializerOptions _serializerOptions; - - /// - /// The HttpClient. - /// - private readonly HttpClient _httpClient; - - /// - /// The logger instance. - /// - private readonly ILogger _logger; - - /// - public Uri NodeAddress { get; } - - /// - /// The internal constructor that setups the client. - /// - /// The url of the RPC server. - /// The possible logger instance. - /// The possible HttpClient instance. If null, a new instance will be created. - protected JsonRpcClient(string url, ILogger logger = default, HttpClient httpClient = default) - { - _logger = logger; - NodeAddress = new Uri(url); - _httpClient = httpClient ?? new HttpClient { BaseAddress = NodeAddress }; - _serializerOptions = new JsonSerializerOptions - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - Converters = - { - new JsonStringEnumConverter(JsonNamingPolicy.CamelCase) - } - }; - } - - /// - /// Sends a given message as a POST method and returns the deserialized message result based on the type parameter. - /// - /// The type of the result to deserialize from json. - /// The message request. - /// A task that represents the asynchronous operation that holds the request result. - protected async Task> SendRequest(JsonRpcRequest req) - { - RequestResult result; - var requestJson = JsonSerializer.Serialize(req, _serializerOptions); - - try - { - _logger?.LogInformation(new EventId(req.Id, req.Method), $"Sending request: {requestJson}"); - - using (var response = await _httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Post, "/") - { - Content = new StringContent(requestJson, Encoding.UTF8, "application/json") - }).ConfigureAwait(false)) - { - return await HandleResult(req, response).ConfigureAwait(false); - } - } - catch (HttpRequestException e) - { - result = new RequestResult(e.StatusCode ?? System.Net.HttpStatusCode.BadRequest, e.Message); - _logger?.LogDebug(new EventId(req.Id, req.Method), $"Caught exception: {e.Message}"); - return result; - } - catch (Exception e) - { - result = new RequestResult(System.Net.HttpStatusCode.BadRequest, e.Message); - _logger?.LogDebug(new EventId(req.Id, req.Method), $"Caught exception: {e.Message}"); - return result; - } - - - } - - /// - /// Handles the result after sending a request. - /// - /// The type of the result to deserialize from json. - /// The original message request. - /// The response obtained from the request. - /// A task that represents the asynchronous operation that holds the request result. - private async Task> HandleResult(JsonRpcRequest req, HttpResponseMessage response) - { - RequestResult result = new RequestResult(response); - if (!result.WasHttpRequestSuccessful) return result; - - try - { - var requestRes = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - - _logger?.LogInformation(new EventId(req.Id, req.Method), $"Result: {requestRes}"); - - var res = JsonSerializer.Deserialize>(requestRes, _serializerOptions); - - if (res.Result != null) - { - result.Result = res.Result; - result.WasRequestSuccessfullyHandled = true; - } - else - { - var errorRes = JsonSerializer.Deserialize(requestRes, _serializerOptions); - if (errorRes is { Error: { } }) - { - result.Reason = errorRes.Error.Message; - result.ServerErrorCode = errorRes.Error.Code; - } - else - { - result.Reason = "Something wrong happened."; - } - } - } - catch (JsonException e) - { - _logger?.LogDebug(new EventId(req.Id, req.Method), $"Caught exception: {e.Message}"); - result.WasRequestSuccessfullyHandled = false; - result.Reason = "Unable to parse json."; - } - - return result; - } - } +using Microsoft.Extensions.Logging; +using Solnet.Rpc.Messages; +using System; +using System.Net.Http; +using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Threading.Tasks; + +namespace Solnet.Rpc.Core.Http +{ + /// + /// Base Rpc client class that abstracts the HttpClient handling. + /// + public abstract class JsonRpcClient + { + /// + /// The Json serializer options to be reused between calls. + /// + private readonly JsonSerializerOptions _serializerOptions; + + /// + /// The HttpClient. + /// + private readonly HttpClient _httpClient; + + /// + /// The logger instance. + /// + private readonly ILogger _logger; + + /// + public Uri NodeAddress { get; } + + /// + /// The internal constructor that setups the client. + /// + /// The url of the RPC server. + /// The possible logger instance. + /// The possible HttpClient instance. If null, a new instance will be created. + protected JsonRpcClient(string url, ILogger logger = default, HttpClient httpClient = default) + { + _logger = logger; + NodeAddress = new Uri(url); + _httpClient = httpClient ?? new HttpClient { BaseAddress = NodeAddress }; + _serializerOptions = new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + Converters = + { + new JsonStringEnumConverter(JsonNamingPolicy.CamelCase) + } + }; + } + + /// + /// Sends a given message as a POST method and returns the deserialized message result based on the type parameter. + /// + /// The type of the result to deserialize from json. + /// The message request. + /// A task that represents the asynchronous operation that holds the request result. + protected async Task> SendRequest(JsonRpcRequest req) + { + RequestResult result; + var requestJson = JsonSerializer.Serialize(req, _serializerOptions); + + try + { + _logger?.LogInformation(new EventId(req.Id, req.Method), $"Sending request: {requestJson}"); + + using (var response = await _httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Post, "/") + { + Content = new StringContent(requestJson, Encoding.UTF8, "application/json") + }).ConfigureAwait(false)) + { + return await HandleResult(req, response).ConfigureAwait(false); + } + } + catch (HttpRequestException e) + { + result = new RequestResult(e.StatusCode ?? System.Net.HttpStatusCode.BadRequest, e.Message); + _logger?.LogDebug(new EventId(req.Id, req.Method), $"Caught exception: {e.Message}"); + return result; + } + catch (Exception e) + { + result = new RequestResult(System.Net.HttpStatusCode.BadRequest, e.Message); + _logger?.LogDebug(new EventId(req.Id, req.Method), $"Caught exception: {e.Message}"); + return result; + } + + + } + + /// + /// Handles the result after sending a request. + /// + /// The type of the result to deserialize from json. + /// The original message request. + /// The response obtained from the request. + /// A task that represents the asynchronous operation that holds the request result. + private async Task> HandleResult(JsonRpcRequest req, HttpResponseMessage response) + { + RequestResult result = new RequestResult(response); + if (!result.WasHttpRequestSuccessful) return result; + + try + { + var requestRes = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + + _logger?.LogInformation(new EventId(req.Id, req.Method), $"Result: {requestRes}"); + + var res = JsonSerializer.Deserialize>(requestRes, _serializerOptions); + + if (res.Result != null) + { + result.Result = res.Result; + result.WasRequestSuccessfullyHandled = true; + } + else + { + var errorRes = JsonSerializer.Deserialize(requestRes, _serializerOptions); + if (errorRes is { Error: { } }) + { + result.Reason = errorRes.Error.Message; + result.ServerErrorCode = errorRes.Error.Code; + } + else + { + result.Reason = "Something wrong happened."; + } + } + } + catch (JsonException e) + { + _logger?.LogDebug(new EventId(req.Id, req.Method), $"Caught exception: {e.Message}"); + result.WasRequestSuccessfullyHandled = false; + result.Reason = "Unable to parse json."; + } + + return result; + } + } } \ No newline at end of file diff --git a/src/Solnet.Rpc/Core/Http/RequestResult.cs b/src/Solnet.Rpc/Core/Http/RequestResult.cs index fa5af044..4cb6cead 100644 --- a/src/Solnet.Rpc/Core/Http/RequestResult.cs +++ b/src/Solnet.Rpc/Core/Http/RequestResult.cs @@ -1,38 +1,38 @@ -using System.Net; -using System.Net.Http; - -namespace Solnet.Rpc.Core.Http -{ - public class RequestResult - { - - public bool WasSuccessful { get => WasHttpRequestSuccessful && WasRequestSuccessfullyHandled; } - - public bool WasHttpRequestSuccessful { get; } - - public bool WasRequestSuccessfullyHandled { get; internal set; } - - public string Reason { get; internal set; } - - public T Result { get; internal set; } - - public HttpStatusCode HttpStatusCode { get; } - - public int ServerErrorCode { get; internal set; } - - internal RequestResult(HttpResponseMessage resultMsg, T result = default(T)) - { - HttpStatusCode = resultMsg.StatusCode; - WasHttpRequestSuccessful = resultMsg.IsSuccessStatusCode; - Reason = resultMsg.ReasonPhrase; - Result = result; - } - - internal RequestResult(HttpStatusCode code, string reason) - { - HttpStatusCode = code; - Reason = reason; - WasHttpRequestSuccessful = false; - } - } +using System.Net; +using System.Net.Http; + +namespace Solnet.Rpc.Core.Http +{ + public class RequestResult + { + + public bool WasSuccessful { get => WasHttpRequestSuccessful && WasRequestSuccessfullyHandled; } + + public bool WasHttpRequestSuccessful { get; } + + public bool WasRequestSuccessfullyHandled { get; internal set; } + + public string Reason { get; internal set; } + + public T Result { get; internal set; } + + public HttpStatusCode HttpStatusCode { get; } + + public int ServerErrorCode { get; internal set; } + + internal RequestResult(HttpResponseMessage resultMsg, T result = default(T)) + { + HttpStatusCode = resultMsg.StatusCode; + WasHttpRequestSuccessful = resultMsg.IsSuccessStatusCode; + Reason = resultMsg.ReasonPhrase; + Result = result; + } + + internal RequestResult(HttpStatusCode code, string reason) + { + HttpStatusCode = code; + Reason = reason; + WasHttpRequestSuccessful = false; + } + } } \ No newline at end of file diff --git a/src/Solnet.Rpc/Core/IdGenerator.cs b/src/Solnet.Rpc/Core/IdGenerator.cs index 838ae969..131680e3 100644 --- a/src/Solnet.Rpc/Core/IdGenerator.cs +++ b/src/Solnet.Rpc/Core/IdGenerator.cs @@ -1,26 +1,26 @@ -namespace Solnet.Rpc.Core -{ - /// - /// Id generator. - /// - internal class IdGenerator - { - - /// - /// The id of the last request performed - /// - private int _id; - - /// - /// Gets the id of the next request. - /// - /// The id. - internal int GetNextId() - { - lock (this) - { - return _id++; - } - } - } +namespace Solnet.Rpc.Core +{ + /// + /// Id generator. + /// + internal class IdGenerator + { + + /// + /// The id of the last request performed + /// + private int _id; + + /// + /// Gets the id of the next request. + /// + /// The id. + internal int GetNextId() + { + lock (this) + { + return _id++; + } + } + } } \ No newline at end of file diff --git a/src/Solnet.Rpc/Core/Sockets/IWebSocket.cs b/src/Solnet.Rpc/Core/Sockets/IWebSocket.cs index c8b9ccbc..f7d6ff9b 100644 --- a/src/Solnet.Rpc/Core/Sockets/IWebSocket.cs +++ b/src/Solnet.Rpc/Core/Sockets/IWebSocket.cs @@ -1,17 +1,17 @@ -using System; -using System.Net.WebSockets; -using System.Threading; -using System.Threading.Tasks; - -namespace Solnet.Rpc.Core.Sockets -{ - public interface IWebSocket : IDisposable - { - WebSocketState State { get; } - - Task ConnectAsync(Uri uri, CancellationToken cancellationToken); - ValueTask SendAsync(ReadOnlyMemory buffer, WebSocketMessageType messageType, bool endOfMessage, CancellationToken cancellationToken); - Task CloseAsync(WebSocketCloseStatus closeStatus, string statusDescription, CancellationToken cancellationToken); - ValueTask ReceiveAsync(Memory buffer, CancellationToken cancellationToken); - } +using System; +using System.Net.WebSockets; +using System.Threading; +using System.Threading.Tasks; + +namespace Solnet.Rpc.Core.Sockets +{ + public interface IWebSocket : IDisposable + { + WebSocketState State { get; } + + Task ConnectAsync(Uri uri, CancellationToken cancellationToken); + ValueTask SendAsync(ReadOnlyMemory buffer, WebSocketMessageType messageType, bool endOfMessage, CancellationToken cancellationToken); + Task CloseAsync(WebSocketCloseStatus closeStatus, string statusDescription, CancellationToken cancellationToken); + ValueTask ReceiveAsync(Memory buffer, CancellationToken cancellationToken); + } } \ No newline at end of file diff --git a/src/Solnet.Rpc/Core/Sockets/StreamingRpcClient.cs b/src/Solnet.Rpc/Core/Sockets/StreamingRpcClient.cs index 4ab4c9fb..ddeb4776 100644 --- a/src/Solnet.Rpc/Core/Sockets/StreamingRpcClient.cs +++ b/src/Solnet.Rpc/Core/Sockets/StreamingRpcClient.cs @@ -1,119 +1,119 @@ -using Microsoft.Extensions.Logging; -using System; -using System.IO; -using System.Net.WebSockets; -using System.Threading; -using System.Threading.Tasks; - -namespace Solnet.Rpc.Core.Sockets -{ - /// - /// Base streaming Rpc client class that abstracts the websocket handling. - /// - public abstract class StreamingRpcClient - { - /// - /// The web socket client abstraction. - /// - protected readonly IWebSocket ClientSocket; - - /// - /// The logger instance. - /// - protected readonly ILogger _logger; - - /// - public Uri NodeAddress { get; } - - /// - /// The internal constructor that setups the client. - /// - /// The url of the streaming RPC server. - /// The possible logger instance. - /// The possible websocket instance. A new instance will be created if null. - protected StreamingRpcClient(string url, ILogger logger, IWebSocket socket = default) - { - NodeAddress = new Uri(url); - ClientSocket = socket ?? new WebSocketWrapper(new ClientWebSocket()); - _logger = logger; - } - - /// - /// Initializes the websocket connection and starts receiving messages asynchronously. - /// - /// Returns the task representing the asynchronous task. - public async Task Init() - { - await ClientSocket.ConnectAsync(NodeAddress, CancellationToken.None).ConfigureAwait(false); - _ = Task.Run(StartListening); - } - - /// - /// Starts listeing to new messages. - /// - /// Returns the task representing the asynchronous task. - private async Task StartListening() - { - while (ClientSocket.State == WebSocketState.Open) - { - try - { - await ReadNextMessage().ConfigureAwait(false); - } - catch (Exception e) - { - _logger?.LogDebug(new EventId(), e, "Exception trying to read next message."); - } - } - _logger?.LogDebug(new EventId(), $"Stopped reading messages. ClientSocket.State changed to {ClientSocket.State}"); - } - - /// - /// Reads the next message from the socket. - /// - /// The cancelation token. - /// Returns the task representing the asynchronous task. - private async Task ReadNextMessage(CancellationToken cancellationToken = default) - { - var buffer = new byte[32768]; - Memory mem = new Memory(buffer); - ValueWebSocketReceiveResult result = await ClientSocket.ReceiveAsync(mem, cancellationToken).ConfigureAwait(false); - int count = result.Count; - - if (result.MessageType == WebSocketMessageType.Close) - { - await ClientSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, cancellationToken); - } - else - { - if (!result.EndOfMessage) - { - MemoryStream ms = new MemoryStream(); - ms.Write(mem.Span); - - - while (!result.EndOfMessage) - { - result = await ClientSocket.ReceiveAsync(mem, cancellationToken).ConfigureAwait(false); - ms.Write(mem.Slice(0, result.Count).Span); - count += result.Count; - } - - mem = new Memory(ms.ToArray()); - } - else - { - mem = mem.Slice(0, count); - } - - HandleNewMessage(mem); - } - } - - /// - /// Handless a new message payload. - /// - /// The message payload. - protected abstract void HandleNewMessage(Memory messagePayload); - } +using Microsoft.Extensions.Logging; +using System; +using System.IO; +using System.Net.WebSockets; +using System.Threading; +using System.Threading.Tasks; + +namespace Solnet.Rpc.Core.Sockets +{ + /// + /// Base streaming Rpc client class that abstracts the websocket handling. + /// + public abstract class StreamingRpcClient + { + /// + /// The web socket client abstraction. + /// + protected readonly IWebSocket ClientSocket; + + /// + /// The logger instance. + /// + protected readonly ILogger _logger; + + /// + public Uri NodeAddress { get; } + + /// + /// The internal constructor that setups the client. + /// + /// The url of the streaming RPC server. + /// The possible logger instance. + /// The possible websocket instance. A new instance will be created if null. + protected StreamingRpcClient(string url, ILogger logger, IWebSocket socket = default) + { + NodeAddress = new Uri(url); + ClientSocket = socket ?? new WebSocketWrapper(new ClientWebSocket()); + _logger = logger; + } + + /// + /// Initializes the websocket connection and starts receiving messages asynchronously. + /// + /// Returns the task representing the asynchronous task. + public async Task Init() + { + await ClientSocket.ConnectAsync(NodeAddress, CancellationToken.None).ConfigureAwait(false); + _ = Task.Run(StartListening); + } + + /// + /// Starts listeing to new messages. + /// + /// Returns the task representing the asynchronous task. + private async Task StartListening() + { + while (ClientSocket.State == WebSocketState.Open) + { + try + { + await ReadNextMessage().ConfigureAwait(false); + } + catch (Exception e) + { + _logger?.LogDebug(new EventId(), e, "Exception trying to read next message."); + } + } + _logger?.LogDebug(new EventId(), $"Stopped reading messages. ClientSocket.State changed to {ClientSocket.State}"); + } + + /// + /// Reads the next message from the socket. + /// + /// The cancelation token. + /// Returns the task representing the asynchronous task. + private async Task ReadNextMessage(CancellationToken cancellationToken = default) + { + var buffer = new byte[32768]; + Memory mem = new Memory(buffer); + ValueWebSocketReceiveResult result = await ClientSocket.ReceiveAsync(mem, cancellationToken).ConfigureAwait(false); + int count = result.Count; + + if (result.MessageType == WebSocketMessageType.Close) + { + await ClientSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, cancellationToken); + } + else + { + if (!result.EndOfMessage) + { + MemoryStream ms = new MemoryStream(); + ms.Write(mem.Span); + + + while (!result.EndOfMessage) + { + result = await ClientSocket.ReceiveAsync(mem, cancellationToken).ConfigureAwait(false); + ms.Write(mem.Slice(0, result.Count).Span); + count += result.Count; + } + + mem = new Memory(ms.ToArray()); + } + else + { + mem = mem.Slice(0, count); + } + + HandleNewMessage(mem); + } + } + + /// + /// Handless a new message payload. + /// + /// The message payload. + protected abstract void HandleNewMessage(Memory messagePayload); + } } \ No newline at end of file diff --git a/src/Solnet.Rpc/Core/Sockets/SubscriptionChannel.cs b/src/Solnet.Rpc/Core/Sockets/SubscriptionChannel.cs index 4f93e761..d3c9f4e7 100644 --- a/src/Solnet.Rpc/Core/Sockets/SubscriptionChannel.cs +++ b/src/Solnet.Rpc/Core/Sockets/SubscriptionChannel.cs @@ -1,12 +1,12 @@ -namespace Solnet.Rpc.Core.Sockets -{ - public enum SubscriptionChannel - { - Account, - Logs, - Program, - Signature, - Slot, - Root - } +namespace Solnet.Rpc.Core.Sockets +{ + public enum SubscriptionChannel + { + Account, + Logs, + Program, + Signature, + Slot, + Root + } } \ No newline at end of file diff --git a/src/Solnet.Rpc/Core/Sockets/SubscriptionEvent.cs b/src/Solnet.Rpc/Core/Sockets/SubscriptionEvent.cs index 173b18eb..32786970 100644 --- a/src/Solnet.Rpc/Core/Sockets/SubscriptionEvent.cs +++ b/src/Solnet.Rpc/Core/Sockets/SubscriptionEvent.cs @@ -1,38 +1,38 @@ -using System; - -namespace Solnet.Rpc.Core.Sockets -{ - /// - /// Represents an event related to a given subscription. - /// - public class SubscriptionEvent : EventArgs - { - /// - /// The new status of the subscription. - /// - public SubscriptionStatus Status { get; } - - /// - /// A possible error mssage for this event. - /// - public string Error { get; } - - /// - /// A possible error code for this event. - /// - public string Code { get; } - - /// - /// Constructor. - /// - /// The new status. - /// The possible error message. - /// The possible error code. - internal SubscriptionEvent(SubscriptionStatus status, string error = default, string code = default) - { - Status = status; - Error = error; - Code = code; - } - } +using System; + +namespace Solnet.Rpc.Core.Sockets +{ + /// + /// Represents an event related to a given subscription. + /// + public class SubscriptionEvent : EventArgs + { + /// + /// The new status of the subscription. + /// + public SubscriptionStatus Status { get; } + + /// + /// A possible error mssage for this event. + /// + public string Error { get; } + + /// + /// A possible error code for this event. + /// + public string Code { get; } + + /// + /// Constructor. + /// + /// The new status. + /// The possible error message. + /// The possible error code. + internal SubscriptionEvent(SubscriptionStatus status, string error = default, string code = default) + { + Status = status; + Error = error; + Code = code; + } + } } \ No newline at end of file diff --git a/src/Solnet.Rpc/Core/Sockets/SubscriptionState.cs b/src/Solnet.Rpc/Core/Sockets/SubscriptionState.cs index c8117246..86a3d5f2 100644 --- a/src/Solnet.Rpc/Core/Sockets/SubscriptionState.cs +++ b/src/Solnet.Rpc/Core/Sockets/SubscriptionState.cs @@ -1,119 +1,119 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Threading.Tasks; - -namespace Solnet.Rpc.Core.Sockets -{ - /// - /// Represents the state of a given subscription. - /// - public abstract class SubscriptionState - { - /// - /// Streaming client reference for easy unsubscription. - /// - private readonly SolanaStreamingRpcClient _rpcClient; - - /// - /// The subscription ID as confirmed by the node. - /// - internal int SubscriptionId { get; set; } - - /// - /// The channel subscribed. - /// - public SubscriptionChannel Channel { get; } - - /// - /// The current state of the subscription. - /// - public SubscriptionStatus State { get; private set; } - - /// - /// The last error message. - /// - public string LastError { get; private set; } - - /// - /// The last error code. - /// - public string LastCode { get; private set; } - - private ImmutableList AdditionalParameters { get; } - - /// - /// Event fired when the state of the subcription changes. - /// - public event EventHandler SubscriptionChanged; - - /// - /// Base constructor. - /// - /// The streaming rpc client reference. - /// The channel of this subscription. - /// Aditional parameters for this given subscription. - internal SubscriptionState(SolanaStreamingRpcClient rpcClient, SubscriptionChannel chan, IList aditionalParameters = default) - { - _rpcClient = rpcClient; - Channel = chan; - AdditionalParameters = aditionalParameters?.ToImmutableList(); - } - - /// - /// Changes the state of the subscription and invokes the event. - /// - /// The new state of the subscription. - /// The possible error message. - /// The possible error code. - internal void ChangeState(SubscriptionStatus newState, string error = null, string code = null) - { - State = newState; - LastError = error; - LastCode = code; - SubscriptionChanged?.Invoke(this, new SubscriptionEvent(newState, error, code)); - } - - /// - /// Invokes the data handler. - /// - /// The data. - internal abstract void HandleData(object data); - - /// - /// Unsubscribes the current subscription. - /// - public void Unsubscribe() => _rpcClient.Unsubscribe(this); - - /// - public async Task UnsubscribeAsync() => await _rpcClient.UnsubscribeAsync(this).ConfigureAwait(false); - } - - /// - /// Represents the state of a given subscription with specified type handler. - /// - /// The type of the data received by this subscription. - internal class SubscriptionState : SubscriptionState - { - /// - /// The data handler reference. - /// - internal Action, T> DataHandler; - - /// - /// Constructor with all parameters related to a given subscription. - /// - /// The streaming rpc client reference. - /// The channel of this subscription. - /// The handler for the data received. - /// Aditional parameters for this given subscription. - internal SubscriptionState(SolanaStreamingRpcClient rpcClient, SubscriptionChannel chan, Action handler, IList aditionalParameters = default) - : base(rpcClient, chan, aditionalParameters) - { - DataHandler = handler; - } - - /// - internal override void HandleData(object data) => DataHandler(this, (T)data); - } +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Threading.Tasks; + +namespace Solnet.Rpc.Core.Sockets +{ + /// + /// Represents the state of a given subscription. + /// + public abstract class SubscriptionState + { + /// + /// Streaming client reference for easy unsubscription. + /// + private readonly SolanaStreamingRpcClient _rpcClient; + + /// + /// The subscription ID as confirmed by the node. + /// + internal int SubscriptionId { get; set; } + + /// + /// The channel subscribed. + /// + public SubscriptionChannel Channel { get; } + + /// + /// The current state of the subscription. + /// + public SubscriptionStatus State { get; private set; } + + /// + /// The last error message. + /// + public string LastError { get; private set; } + + /// + /// The last error code. + /// + public string LastCode { get; private set; } + + private ImmutableList AdditionalParameters { get; } + + /// + /// Event fired when the state of the subcription changes. + /// + public event EventHandler SubscriptionChanged; + + /// + /// Base constructor. + /// + /// The streaming rpc client reference. + /// The channel of this subscription. + /// Aditional parameters for this given subscription. + internal SubscriptionState(SolanaStreamingRpcClient rpcClient, SubscriptionChannel chan, IList aditionalParameters = default) + { + _rpcClient = rpcClient; + Channel = chan; + AdditionalParameters = aditionalParameters?.ToImmutableList(); + } + + /// + /// Changes the state of the subscription and invokes the event. + /// + /// The new state of the subscription. + /// The possible error message. + /// The possible error code. + internal void ChangeState(SubscriptionStatus newState, string error = null, string code = null) + { + State = newState; + LastError = error; + LastCode = code; + SubscriptionChanged?.Invoke(this, new SubscriptionEvent(newState, error, code)); + } + + /// + /// Invokes the data handler. + /// + /// The data. + internal abstract void HandleData(object data); + + /// + /// Unsubscribes the current subscription. + /// + public void Unsubscribe() => _rpcClient.Unsubscribe(this); + + /// + public async Task UnsubscribeAsync() => await _rpcClient.UnsubscribeAsync(this).ConfigureAwait(false); + } + + /// + /// Represents the state of a given subscription with specified type handler. + /// + /// The type of the data received by this subscription. + internal class SubscriptionState : SubscriptionState + { + /// + /// The data handler reference. + /// + internal Action, T> DataHandler; + + /// + /// Constructor with all parameters related to a given subscription. + /// + /// The streaming rpc client reference. + /// The channel of this subscription. + /// The handler for the data received. + /// Aditional parameters for this given subscription. + internal SubscriptionState(SolanaStreamingRpcClient rpcClient, SubscriptionChannel chan, Action handler, IList aditionalParameters = default) + : base(rpcClient, chan, aditionalParameters) + { + DataHandler = handler; + } + + /// + internal override void HandleData(object data) => DataHandler(this, (T)data); + } } \ No newline at end of file diff --git a/src/Solnet.Rpc/Core/Sockets/SubscriptionStatus.cs b/src/Solnet.Rpc/Core/Sockets/SubscriptionStatus.cs index 640c7286..e4fd227d 100644 --- a/src/Solnet.Rpc/Core/Sockets/SubscriptionStatus.cs +++ b/src/Solnet.Rpc/Core/Sockets/SubscriptionStatus.cs @@ -1,28 +1,28 @@ -namespace Solnet.Rpc.Core.Sockets -{ - /// - /// Represents the status of a subscription. - /// - public enum SubscriptionStatus - { - /// - /// Waiting for the subscription message to be handled. - /// - WaitingResult, - - /// - /// The subscription was terminated. - /// - Unsubscribed, - - /// - /// The subscription is still alive. - /// - Subscribed, - - /// - /// There was an error during subscription. - /// - ErrorSubscribing - } +namespace Solnet.Rpc.Core.Sockets +{ + /// + /// Represents the status of a subscription. + /// + public enum SubscriptionStatus + { + /// + /// Waiting for the subscription message to be handled. + /// + WaitingResult, + + /// + /// The subscription was terminated. + /// + Unsubscribed, + + /// + /// The subscription is still alive. + /// + Subscribed, + + /// + /// There was an error during subscription. + /// + ErrorSubscribing + } } \ No newline at end of file diff --git a/src/Solnet.Rpc/Core/Sockets/WebSocketWrapper.cs b/src/Solnet.Rpc/Core/Sockets/WebSocketWrapper.cs index 1c0bbb27..71efdac5 100644 --- a/src/Solnet.Rpc/Core/Sockets/WebSocketWrapper.cs +++ b/src/Solnet.Rpc/Core/Sockets/WebSocketWrapper.cs @@ -1,53 +1,53 @@ -using System; -using System.Net.WebSockets; -using System.Threading; -using System.Threading.Tasks; - -namespace Solnet.Rpc.Core.Sockets -{ - internal class WebSocketWrapper : IWebSocket - { - private readonly ClientWebSocket webSocket; - - internal WebSocketWrapper(ClientWebSocket webSocket) - { - this.webSocket = webSocket; - } - - public WebSocketState State => webSocket.State; - - public Task CloseAsync(WebSocketCloseStatus closeStatus, string statusDescription, CancellationToken cancellationToken) - => webSocket.CloseAsync(closeStatus, statusDescription, cancellationToken); - - public Task ConnectAsync(Uri uri, CancellationToken cancellationToken) - => webSocket.ConnectAsync(uri, cancellationToken); - - public ValueTask ReceiveAsync(Memory buffer, CancellationToken cancellationToken) - => webSocket.ReceiveAsync(buffer, cancellationToken); - - public ValueTask SendAsync(ReadOnlyMemory buffer, WebSocketMessageType messageType, bool endOfMessage, CancellationToken cancellationToken) - => webSocket.SendAsync(buffer, messageType, endOfMessage, cancellationToken); - - #region IDisposable Support - private bool disposedValue = false; // To detect redundant calls - - private void Dispose(bool disposing) - { - if (!disposedValue) - { - if (disposing) - { - webSocket.Dispose(); - } - - disposedValue = true; - } - } - - public void Dispose() - { - Dispose(true); - } - #endregion - } +using System; +using System.Net.WebSockets; +using System.Threading; +using System.Threading.Tasks; + +namespace Solnet.Rpc.Core.Sockets +{ + internal class WebSocketWrapper : IWebSocket + { + private readonly ClientWebSocket webSocket; + + internal WebSocketWrapper(ClientWebSocket webSocket) + { + this.webSocket = webSocket; + } + + public WebSocketState State => webSocket.State; + + public Task CloseAsync(WebSocketCloseStatus closeStatus, string statusDescription, CancellationToken cancellationToken) + => webSocket.CloseAsync(closeStatus, statusDescription, cancellationToken); + + public Task ConnectAsync(Uri uri, CancellationToken cancellationToken) + => webSocket.ConnectAsync(uri, cancellationToken); + + public ValueTask ReceiveAsync(Memory buffer, CancellationToken cancellationToken) + => webSocket.ReceiveAsync(buffer, cancellationToken); + + public ValueTask SendAsync(ReadOnlyMemory buffer, WebSocketMessageType messageType, bool endOfMessage, CancellationToken cancellationToken) + => webSocket.SendAsync(buffer, messageType, endOfMessage, cancellationToken); + + #region IDisposable Support + private bool disposedValue = false; // To detect redundant calls + + private void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + webSocket.Dispose(); + } + + disposedValue = true; + } + } + + public void Dispose() + { + Dispose(true); + } + #endregion + } } \ No newline at end of file diff --git a/src/Solnet.Rpc/IRpcClient.cs b/src/Solnet.Rpc/IRpcClient.cs index b53207d6..be8fb948 100644 --- a/src/Solnet.Rpc/IRpcClient.cs +++ b/src/Solnet.Rpc/IRpcClient.cs @@ -1,712 +1,712 @@ -using Solnet.Rpc.Core.Http; -using Solnet.Rpc.Messages; -using Solnet.Rpc.Models; -using Solnet.Rpc.Types; -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Solnet.Rpc -{ - /// - /// Specifies the methods to interact with the JSON RPC API. - /// - public interface IRpcClient - { - /// - /// The address this client connects to. - /// - Uri NodeAddress { get; } - - /// - /// Gets the account info using base64 encoding. This is an asynchronous operation. - /// - /// The commitment parameter is optional, the default value is not sent. - /// - /// - /// The account public key. - /// The state commitment to consider when querying the ledger state. - /// A task which may return a request result holding the context and account info. - Task>> GetAccountInfoAsync(string pubKey, Commitment commitment = Commitment.Finalized); - - /// - /// Gets the account info using base64 encoding. This is a synchronous operation. - /// - /// The commitment parameter is optional, the default value is not sent. - /// - /// - /// The account public key. - /// The state commitment to consider when querying the ledger state. - /// Returns an object that wraps the result along with possible errors with the request. - RequestResult> GetAccountInfo(string pubKey, Commitment commitment = Commitment.Finalized); - - /// - /// Returns all accounts owned by the provided program Pubkey. This is an asynchronous operation. - /// - /// The program public key. - /// A task which may return a request result holding the context and account info. - Task>> GetProgramAccountsAsync(string pubKey); - - /// - /// Returns all accounts owned by the provided program Pubkey. This is a synchronous operation. - /// - /// The program public key. - /// Returns an object that wraps the result along with possible errors with the request. - RequestResult> GetProgramAccounts(string pubKey); - - /// - /// Gets the account info for multiple accounts. This is an asynchronous operation. - /// - /// The list of the accounts public keys. - /// A task which may return a request result holding the context and account info. - Task>>> GetMultipleAccountsAsync(IList accounts); - - /// - /// Gets the account info for multiple accounts. This is a synchronous operation. - /// - /// The list of the accounts public keys. - /// Returns an object that wraps the result along with possible errors with the request. - RequestResult>> GetMultipleAccounts(IList accounts); - - /// - /// Gets the balance asynchronously for a certain public key. - /// - /// The commitment parameter is optional, the default value is not sent. - /// - /// - /// The public key. - /// The state commitment to consider when querying the ledger state. - /// A task which may return a request result holding the context and address balance. - Task>> GetBalanceAsync(string pubKey, Commitment commitment = Commitment.Finalized); - - /// - /// Gets the balance synchronously for a certain public key. - /// - /// The commitment parameter is optional, the default value is not sent. - /// - /// - /// The public key. - /// The state commitment to consider when querying the ledger state. - /// Returns an object that wraps the result along with possible errors with the request. - RequestResult> GetBalance(string pubKey, Commitment commitment = Commitment.Finalized); - - /// - /// Returns identity and transaction information about a confirmed block in the ledger. This is an asynchronous operation. - /// - /// The commitment parameter is optional, the default value is not sent. - /// - /// - /// The slot. - /// The state commitment to consider when querying the ledger state. - /// Returns a task that holds the asynchronous operation result and state. - Task> GetBlockAsync(ulong slot, Commitment commitment = Commitment.Finalized); - - /// - /// Returns identity and transaction information about a confirmed block in the ledger. This is a synchronous operation. - /// - /// The commitment parameter is optional, the default value is not sent. - /// - /// - /// The slot. - /// The state commitment to consider when querying the ledger state. - /// Returns an object that wraps the result along with possible errors with the request. - RequestResult GetBlock(ulong slot, Commitment commitment = Commitment.Finalized); - - /// - /// Returns recent block production information from the current or previous epoch. This is an asynchronous operation. - /// - /// Returns a task that holds the asynchronous operation result and state. - Task>> GetBlockProductionAsync(); - - /// - /// Returns recent block production information from the current or previous epoch. This is a synchronous operation. - /// - /// Returns an object that wraps the result along with possible errors with the request. - RequestResult> GetBlockProduction(); - - /// - /// Returns recent block production information from the current or previous epoch. This is an asynchronous operation. - /// - /// The first slot to return production information (inclusive). - /// The last slot to return production information (inclusive and optional). - /// Returns a task that holds the asynchronous operation result and state. - Task>> GetBlockProductionAsync(ulong firstSlot, ulong lastSlot = 0); - - /// - /// Returns recent block production information from the current or previous epoch. This is a synchronous operation. - /// - /// The first slot to return production information (inclusive). - /// The last slot to return production information (inclusive and optional). - /// Returns an object that wraps the result along with possible errors with the request. - RequestResult> GetBlockProduction(ulong firstSlot, ulong lastSlot = 0); - - /// - /// Returns recent block production information from the current or previous epoch. This is an asynchronous operation. - /// - /// Filter production details only for this given validator. - /// Returns a task that holds the asynchronous operation result and state. - Task>> GetBlockProductionAsync(string identity); - - /// - /// Returns recent block production information from the current or previous epoch. This is a synchronous operation. - /// - /// Filter production details only for this given validator. - /// Returns an object that wraps the result along with possible errors with the request. - RequestResult> GetBlockProduction(string identity); - - /// - /// Returns recent block production information from the current or previous epoch. This is an asynchronous operation. - /// - /// Filter production details only for this given validator. - /// The first slot to return production information (inclusive). - /// The last slot to return production information (inclusive and optional). - /// Returns a task that holds the asynchronous operation result and state. - Task>> GetBlockProductionAsync(string identity, ulong firstSlot, ulong lastSlot = 0); - - /// - /// Returns recent block production information from the current or previous epoch. This is a synchronous operation. - /// - /// Filter production details only for this given validator. - /// The first slot to return production information (inclusive). - /// The last slot to return production information (inclusive and optional). - /// Returns an object that wraps the result along with possible errors with the request. - RequestResult> GetBlockProduction(string identity, ulong firstSlot, ulong lastSlot = 0); - - /// - /// Returns transaction details for a confirmed transaction. This is an asynchronous operation. - /// - /// ransaction signature as base-58 encoded string. - /// Returns a task that holds the asynchronous operation result and state. - Task> GetTransactionAsync(string signature); - - /// - /// Returns transaction details for a confirmed transaction. This is a synchronous operation. - /// - /// Transaction signature as base-58 encoded string. - /// Returns an object that wraps the result along with possible errors with the request. - RequestResult GetTransaction(string signature); - - /// - /// Returns a list of confirmed blocks between two slots. This is a synchronous operation. - /// - /// The start slot (inclusive). - /// The start slot (inclusive and optional). - /// Returns an object that wraps the result along with possible errors with the request. - RequestResult> GetBlocks(ulong startSlot, ulong endSlot = 0); - - /// - /// Returns a list of confirmed blocks between two slots. This is an asynchronous operation. - /// - /// The start slot (inclusive). - /// The start slot (inclusive and optional). - /// Returns a task that holds the asynchronous operation result and state. - Task>> GetBlocksAsync(ulong startSlot, ulong endSlot = 0); - - /// - /// Returns a list of confirmed blocks starting at the given slot. This is a synchronous operation. - /// - /// The start slot (inclusive). - /// The max number of blocks to return. - /// Returns an object that wraps the result along with possible errors with the request. - RequestResult> GetBlocksWithLimit(ulong startSlot, ulong limit); - - /// - /// Returns a list of confirmed blocks starting at the given slot. This is an asynchronous operation. - /// - /// The start slot (inclusive). - /// The max number of blocks to return. - /// Returns a task that holds the asynchronous operation result and state. - Task>> GetBlocksWithLimitAsync(ulong startSlot, ulong limit); - - /// - /// Returns the slot of the lowest confirmed block that has not been purged from the ledger. This is a synchronous operation. - /// - /// Returns an object that wraps the result along with possible errors with the request. - RequestResult GetFirstAvailableBlock(); - - /// - /// Returns the slot of the lowest confirmed block that has not been purged from the ledger. This is an asynchronous operation. - /// - /// Returns a task that holds the asynchronous operation result and state. - Task> GetFirstAvailableBlockAsync(); - - /// - /// Returns the current health of the node. - /// This method should return the strink 'ok' if the node is healthy, or the error code along with any information provided otherwise. - /// This is a synchronous operation. - /// - /// Returns an object that wraps the result along with possible errors with the request. - RequestResult GetHealth(); - - /// - /// Returns the current health of the node. - /// This method should return the strink 'ok' if the node is healthy, or the error code along with any information provided otherwise. - /// This is an asynchronous operation. - /// - /// Returns a task that holds the asynchronous operation result and state. - Task> GetHealthAsync(); - - /// - /// Returns the leader schedule for an epoch. This is a synchronous operation. - /// - /// Fetch the leader schedule for the epoch that corresponds to the provided slot. - /// If unspecified, the leader schedule for the current epoch is fetched. - /// Filter results for this validator only (base 58 encoded string and optional). - /// Returns an object that wraps the result along with possible errors with the request. - RequestResult>> GetLeaderSchedule(ulong slot = 0, string identity = null); - - /// - /// Returns the leader schedule for an epoch. This is an asynchronous operation. - /// - /// Fetch the leader schedule for the epoch that corresponds to the provided slot. - /// If unspecified, the leader schedule for the current epoch is fetched. - /// Filter results for this validator only (base 58 encoded string and optional). - /// Returns a task that holds the asynchronous operation result and state. - Task>>> GetLeaderScheduleAsync(ulong slot = 0, string identity = null); - - /// - /// Gets the block commitment of a certain block, identified by slot. - /// - /// The slot. - /// A task which may return a request result and block commitment information. - Task> GetBlockCommitmentAsync(ulong slot); - - /// - RequestResult GetBlockCommitment(ulong slot); - - /// - /// Gets the estimated production time for a certain block, identified by slot. - /// - /// The slot. - /// A task which may return a request result and block production time as Unix timestamp (seconds since Epoch). - Task> GetBlockTimeAsync(ulong slot); - - /// - RequestResult GetBlockTime(ulong slot); - - /// - /// Gets the current block height of the node. - /// - /// A task which may return a request result and a block height. - Task> GetBlockHeightAsync(); - - /// - RequestResult GetBlockHeight(); - - /// - /// Gets the cluster nodes. - /// - /// A task which may return a request result and information about the nodes participating in the cluster. - Task>> GetClusterNodesAsync(); - - /// - RequestResult> GetClusterNodes(); - - /// - /// Gets information about the current epoch. - /// - /// A task which may return a request result and information about the current epoch. - Task> GetEpochInfoAsync(); - - /// - RequestResult GetEpochInfo(); - - /// - /// Gets epoch schedule information from this cluster's genesis config. - /// - /// A task which may return a request result and epoch schedule information from this cluster's genesis config. - Task> GetEpochScheduleAsync(); - - /// - RequestResult GetEpochSchedule(); - - /// - /// Gets the fee calculator associated with the query blockhash, or null if the blockhash has expired. - /// - /// The blockhash to query, as base-58 encoded string. - /// A task which may return a request result and the fee calculator for the block. - Task>> GetFeeCalculatorForBlockhashAsync(string blockhash); - - /// - RequestResult> GetFeeCalculatorForBlockhash(string blockhash); - - /// - /// Gets the fee rate governor information from the root bank. - /// - /// A task which may return a request result and the fee rate governor. - Task>> GetFeeRateGovernorAsync(); - - /// - RequestResult> GetFeeRateGovernor(); - - /// - /// Gets a recent block hash from the ledger, a fee schedule that can be used to compute the - /// cost of submitting a transaction using it, and the last slot in which the blockhash will be valid. - /// - /// A task which may return a request result and information about fees. - Task>> GetFeesAsync(); - - /// - RequestResult> GetFees(); - - /// - /// Gets a recent block hash. - /// - /// A task which may return a request result and recent block hash. - Task>> GetRecentBlockHashAsync(); - - /// - RequestResult> GetRecentBlockHash(); - - /// - /// Gets the minimum balance required to make account rent exempt. - /// - /// The account data size. - /// A task which may return a request result and the rent exemption value. - Task> GetMinimumBalanceForRentExemptionAsync(long accountDataSize); - - /// - RequestResult GetMinimumBalanceForRentExemption(long accountDataSize); - - /// - /// Gets the genesis hash of the ledger. - /// - /// A task which may return a request result and a string representing the genesis hash. - Task> GetGenesisHashAsync(); - - /// - RequestResult GetGenesisHash(); - - /// - /// Gets the identity pubkey for the current node. - /// - /// A task which may return a request result and an object with the identity public key. - Task> GetIdentityAsync(); - - /// - RequestResult GetIdentity(); - - /// - /// Gets the current inflation governor. - /// - /// A task which may return a request result and an object representing the current inflation governor. - Task> GetInflationGovernorAsync(); - - /// - RequestResult GetInflationGovernor(); - - /// - /// Gets the specific inflation values for the current epoch. - /// - /// A task which may return a request result and an object representing the current inflation rate. - Task> GetInflationRateAsync(); - - /// - RequestResult GetInflationRate(); - - /// - /// Gets the inflation reward for a list of addresses for an epoch. - /// - /// The list of addresses to query, as base-58 encoded strings. - /// The epoch. - /// A task which may return a request result and a list of objects representing the inflation reward. - Task>> GetInflationRewardAsync(List addresses, ulong epoch = 0); - - /// - RequestResult> GetInflationReward(List addresses, ulong epoch = 0); - - /// - /// Gets the 20 largest accounts, by lamport balance. - /// - /// Filter results by account type. - /// A task which may return a request result and a list about the largest accounts. - /// Results may be cached up to two hours. - Task>>> GetLargestAccountsAsync(string filter); - - /// - RequestResult>> GetLargestAccounts(string filter); - - /// - /// Gets the maximum slot seen from retransmit stage. - /// - /// A task which may return a request result the maximum slot. - Task> GetMaxRetransmitSlotAsync(); - - /// - RequestResult GetMaxRetransmitSlot(); - - /// - /// Gets the maximum slot seen from after shred insert. - /// - /// A task which may return a request result the maximum slot. - Task> GetMaxShredInsertSlotAsync(); - - /// - RequestResult GetMaxShredInsertSlot(); - - /// - /// Gets the highest slot that the node has a snapshot for. - /// - /// A task which may return a request result the highest slot with a snapshot. - Task> GetSnapshotSlotAsync(); - - /// - RequestResult GetSnapshotSlot(); - - /// - /// Gets a list of recent performance samples. - /// - /// Unless searchTransactionHistory is included, this method only searches the recent status cache of signatures. - /// - /// - /// Maximum transaction signatures to return, between 1-720. Default is 720. - /// A task which may return a request result the signatures for the transactions. - Task>> GetRecentPerformanceSamplesAsync(ulong limit = 720); - - /// - RequestResult> GetRecentPerformanceSamples(ulong limit = 720); - - /// - /// Gets confirmed signatures for transactions involving the address. - /// - /// Unless searchTransactionHistory is included, this method only searches the recent status cache of signatures. - /// - /// - /// The account address as base-58 encoded string. - /// Maximum transaction signatures to return, between 1-1000. Default is 1000. - /// Start searching backwards from this transaction signature. - /// Search until this transaction signature, if found before limit is reached. - /// A task which may return a request result the signatures for the transactions. - Task>> GetSignaturesForAddressAsync(string accountPubKey, ulong limit = 1000, string before = "", string until = ""); - - /// - RequestResult> GetSignaturesForAddress(string accountPubKey, ulong limit = 1000, string before = "", string until = ""); - - /// - /// Gets the status of a list of signatures. - /// - /// Unless searchTransactionHistory is included, this method only searches the recent status cache of signatures. - /// - /// - /// The list of transactions to search status info for. - /// If the node should search for signatures in it's ledger cache. - /// A task which may return a request result the highest slot with a snapshot. - Task>>> GetSignatureStatusesAsync(List transactionHashes, - bool searchTransactionHistory = false); - - /// - RequestResult>> GetSignatureStatuses(List transactionHashes, - bool searchTransactionHistory = false); - - /// - /// Gets the current slot the node is processing - /// - /// A task which may return a request result the current slot. - Task> GetSlotAsync(); - - /// - RequestResult GetSlot(); - - /// - /// Gets the current slot leader. - /// - /// A task which may return a request result the slot leader. - Task> GetSlotLeaderAsync(); - - /// - RequestResult GetSlotLeader(); - - /// - /// Gets the slot leaders for a given slot range. - /// - /// The start slot. - /// The result limit. - /// A task which may return a request result and a list slot leaders. - Task>> GetSlotLeadersAsync(ulong start, ulong limit); - - /// - RequestResult> GetSlotLeaders(ulong start, ulong limit); - - /// - /// Gets the epoch activation information for a stake account. - /// - /// Public key of account to query, as base-58 encoded string - /// Epoch for which to calculate activation details. - /// A task which may return a request result and information about the stake activation. - Task> GetStakeActivationAsync(string publicKey, ulong epoch = 0); - - /// - RequestResult GetStakeActivation(string publicKey, ulong epoch = 0); - - /// - /// Gets information about the current supply. - /// - /// A task which may return a request result and information about the supply. - Task>> GetSupplyAsync(); - - /// - RequestResult> GetSupply(); - - /// - /// Gets the token balance of an SPL Token account. - /// - /// Public key of Token account to query, as base-58 encoded string. - /// A task which may return a request result and information about the token account balance. - Task>> GetTokenAccountBalanceAsync(string splTokenAccountPublicKey); - - /// - RequestResult> GetTokenAccountBalance(string splTokenAccountPublicKey); - - /// - /// Gets all SPL Token accounts by approved delegate. - /// - /// Public key of account owner query, as base-58 encoded string. - /// Public key of the specific token Mint to limit accounts to, as base-58 encoded string. - /// Public key of the Token program ID that owns the accounts, as base-58 encoded string. - /// A task which may return a request result and information about token accounts by delegate - Task>>> GetTokenAccountsByDelegateAsync( - string ownerPubKey, string tokenMintPubKey = "", string tokenProgramId = ""); - - /// - RequestResult>> GetTokenAccountsByDelegate( - string ownerPubKey, string tokenMintPubKey = "", string tokenProgramId = ""); - - /// - /// Gets all SPL Token accounts by token owner. - /// - /// Public key of account owner query, as base-58 encoded string. - /// Public key of the specific token Mint to limit accounts to, as base-58 encoded string. - /// Public key of the Token program ID that owns the accounts, as base-58 encoded string. - /// A task which may return a request result and information about token accounts by owner. - Task>>> GetTokenAccountsByOwnerAsync( - string ownerPubKey, string tokenMintPubKey = "", string tokenProgramId = ""); - - /// - RequestResult>> GetTokenAccountsByOwner( - string ownerPubKey, string tokenMintPubKey = "", string tokenProgramId = ""); - - /// - /// Gets the 20 largest token accounts of a particular SPL Token. - /// - /// Public key of Token Mint to query, as base-58 encoded string. - /// A task which may return a request result and information about largest token accounts. - Task>>> GetTokenLargestAccountsAsync(string tokenMintPubKey); - - /// - RequestResult>> GetTokenLargestAccounts(string tokenMintPubKey); - - /// - /// Get the token supply of an SPL Token type. - /// - /// Public key of Token Mint to query, as base-58 encoded string. - /// A task which may return a request result and information about the supply. - Task>> GetTokenSupplyAsync(string tokenMintPubKey); - - /// - RequestResult> GetTokenSupply(string tokenMintPubKey); - - /// - /// Gets the total transaction count of the ledger. - /// - /// A task which may return a request result and information about the transaction count. - Task> GetTransactionCountAsync(); - - /// - RequestResult GetTransactionCount(); - - - /// - /// Gets the current node's software version info. - /// - /// A task which may return a request result and information about the current node's software. - Task> GetVersionAsync(); - - /// - RequestResult GetVersion(); - - /// - /// Gets the account info and associated stake for all voting accounts in the current bank. - /// - /// A task which may return a request result and information about the vote accounts. - Task> GetVoteAccountsAsync(); - - /// - RequestResult GetVoteAccounts(); - - /// - /// Gets the lowest slot that the node has information about in its ledger. - /// - /// This value may decrease over time if a node is configured to purging data. - /// - /// - /// A task which may return a request result and the minimum slot available. - Task> GetMinimumLedgerSlotAsync(); - - /// - RequestResult GetMinimumLedgerSlot(); - - /// - /// Requests an airdrop to the passed pubKey of the passed lamports amount. - /// - /// The commitment parameter is optional, the default is used. - /// - /// - /// The public key of to receive the airdrop. - /// The amount of lamports to request. - /// The block commitment used to retrieve block hashes and verify success. - /// A task which may return a request result and the transaction signature of the airdrop, as base-58 encoded string.. - Task> RequestAirdropAsync(string pubKey, ulong lamports, - Commitment commitment = Commitment.Finalized); - - /// - RequestResult RequestAirdrop(string pubKey, ulong lamports, - Commitment commitment = Commitment.Finalized); - - /// - /// Sends a transaction. - /// - /// The signed transaction as base-58 or base-64 encoded string. - /// The encoding of the transaction. - /// - /// A task which may return a request result and the first transaction signature embedded in the transaction, as base-58 encoded string. - /// - Task> SendTransactionAsync(string transaction, - BinaryEncoding encoding = BinaryEncoding.Base64); - - /// - RequestResult SendTransaction(string transaction, BinaryEncoding encoding = BinaryEncoding.Base64); - - /// - /// Sends a transaction. - /// - /// The signed transaction as byte array. - /// - /// A task which may return a request result and the first transaction signature embedded in the transaction, as base-58 encoded string. - /// - RequestResult SendTransaction(byte[] transaction); - - /// - /// Simulate sending a transaction. - /// - /// The signed transaction as a byte array. - /// The encoding of the transaction. - /// - /// A task which may return a request result and the transaction status. - /// - Task>> SimulateTransactionAsync(string transaction, - BinaryEncoding encoding = BinaryEncoding.Base64); - - /// - RequestResult> SimulateTransaction(string transaction, - BinaryEncoding encoding = BinaryEncoding.Base64); - - /// - /// Simulate sending a transaction. - /// - /// The signed transaction as a byte array. - /// - /// A task which may return a request result and the transaction status. - /// - RequestResult> SimulateTransaction(byte[] transaction); - } +using Solnet.Rpc.Core.Http; +using Solnet.Rpc.Messages; +using Solnet.Rpc.Models; +using Solnet.Rpc.Types; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Solnet.Rpc +{ + /// + /// Specifies the methods to interact with the JSON RPC API. + /// + public interface IRpcClient + { + /// + /// The address this client connects to. + /// + Uri NodeAddress { get; } + + /// + /// Gets the account info using base64 encoding. This is an asynchronous operation. + /// + /// The commitment parameter is optional, the default value is not sent. + /// + /// + /// The account public key. + /// The state commitment to consider when querying the ledger state. + /// A task which may return a request result holding the context and account info. + Task>> GetAccountInfoAsync(string pubKey, Commitment commitment = Commitment.Finalized); + + /// + /// Gets the account info using base64 encoding. This is a synchronous operation. + /// + /// The commitment parameter is optional, the default value is not sent. + /// + /// + /// The account public key. + /// The state commitment to consider when querying the ledger state. + /// Returns an object that wraps the result along with possible errors with the request. + RequestResult> GetAccountInfo(string pubKey, Commitment commitment = Commitment.Finalized); + + /// + /// Returns all accounts owned by the provided program Pubkey. This is an asynchronous operation. + /// + /// The program public key. + /// A task which may return a request result holding the context and account info. + Task>> GetProgramAccountsAsync(string pubKey); + + /// + /// Returns all accounts owned by the provided program Pubkey. This is a synchronous operation. + /// + /// The program public key. + /// Returns an object that wraps the result along with possible errors with the request. + RequestResult> GetProgramAccounts(string pubKey); + + /// + /// Gets the account info for multiple accounts. This is an asynchronous operation. + /// + /// The list of the accounts public keys. + /// A task which may return a request result holding the context and account info. + Task>>> GetMultipleAccountsAsync(IList accounts); + + /// + /// Gets the account info for multiple accounts. This is a synchronous operation. + /// + /// The list of the accounts public keys. + /// Returns an object that wraps the result along with possible errors with the request. + RequestResult>> GetMultipleAccounts(IList accounts); + + /// + /// Gets the balance asynchronously for a certain public key. + /// + /// The commitment parameter is optional, the default value is not sent. + /// + /// + /// The public key. + /// The state commitment to consider when querying the ledger state. + /// A task which may return a request result holding the context and address balance. + Task>> GetBalanceAsync(string pubKey, Commitment commitment = Commitment.Finalized); + + /// + /// Gets the balance synchronously for a certain public key. + /// + /// The commitment parameter is optional, the default value is not sent. + /// + /// + /// The public key. + /// The state commitment to consider when querying the ledger state. + /// Returns an object that wraps the result along with possible errors with the request. + RequestResult> GetBalance(string pubKey, Commitment commitment = Commitment.Finalized); + + /// + /// Returns identity and transaction information about a confirmed block in the ledger. This is an asynchronous operation. + /// + /// The commitment parameter is optional, the default value is not sent. + /// + /// + /// The slot. + /// The state commitment to consider when querying the ledger state. + /// Returns a task that holds the asynchronous operation result and state. + Task> GetBlockAsync(ulong slot, Commitment commitment = Commitment.Finalized); + + /// + /// Returns identity and transaction information about a confirmed block in the ledger. This is a synchronous operation. + /// + /// The commitment parameter is optional, the default value is not sent. + /// + /// + /// The slot. + /// The state commitment to consider when querying the ledger state. + /// Returns an object that wraps the result along with possible errors with the request. + RequestResult GetBlock(ulong slot, Commitment commitment = Commitment.Finalized); + + /// + /// Returns recent block production information from the current or previous epoch. This is an asynchronous operation. + /// + /// Returns a task that holds the asynchronous operation result and state. + Task>> GetBlockProductionAsync(); + + /// + /// Returns recent block production information from the current or previous epoch. This is a synchronous operation. + /// + /// Returns an object that wraps the result along with possible errors with the request. + RequestResult> GetBlockProduction(); + + /// + /// Returns recent block production information from the current or previous epoch. This is an asynchronous operation. + /// + /// The first slot to return production information (inclusive). + /// The last slot to return production information (inclusive and optional). + /// Returns a task that holds the asynchronous operation result and state. + Task>> GetBlockProductionAsync(ulong firstSlot, ulong lastSlot = 0); + + /// + /// Returns recent block production information from the current or previous epoch. This is a synchronous operation. + /// + /// The first slot to return production information (inclusive). + /// The last slot to return production information (inclusive and optional). + /// Returns an object that wraps the result along with possible errors with the request. + RequestResult> GetBlockProduction(ulong firstSlot, ulong lastSlot = 0); + + /// + /// Returns recent block production information from the current or previous epoch. This is an asynchronous operation. + /// + /// Filter production details only for this given validator. + /// Returns a task that holds the asynchronous operation result and state. + Task>> GetBlockProductionAsync(string identity); + + /// + /// Returns recent block production information from the current or previous epoch. This is a synchronous operation. + /// + /// Filter production details only for this given validator. + /// Returns an object that wraps the result along with possible errors with the request. + RequestResult> GetBlockProduction(string identity); + + /// + /// Returns recent block production information from the current or previous epoch. This is an asynchronous operation. + /// + /// Filter production details only for this given validator. + /// The first slot to return production information (inclusive). + /// The last slot to return production information (inclusive and optional). + /// Returns a task that holds the asynchronous operation result and state. + Task>> GetBlockProductionAsync(string identity, ulong firstSlot, ulong lastSlot = 0); + + /// + /// Returns recent block production information from the current or previous epoch. This is a synchronous operation. + /// + /// Filter production details only for this given validator. + /// The first slot to return production information (inclusive). + /// The last slot to return production information (inclusive and optional). + /// Returns an object that wraps the result along with possible errors with the request. + RequestResult> GetBlockProduction(string identity, ulong firstSlot, ulong lastSlot = 0); + + /// + /// Returns transaction details for a confirmed transaction. This is an asynchronous operation. + /// + /// ransaction signature as base-58 encoded string. + /// Returns a task that holds the asynchronous operation result and state. + Task> GetTransactionAsync(string signature); + + /// + /// Returns transaction details for a confirmed transaction. This is a synchronous operation. + /// + /// Transaction signature as base-58 encoded string. + /// Returns an object that wraps the result along with possible errors with the request. + RequestResult GetTransaction(string signature); + + /// + /// Returns a list of confirmed blocks between two slots. This is a synchronous operation. + /// + /// The start slot (inclusive). + /// The start slot (inclusive and optional). + /// Returns an object that wraps the result along with possible errors with the request. + RequestResult> GetBlocks(ulong startSlot, ulong endSlot = 0); + + /// + /// Returns a list of confirmed blocks between two slots. This is an asynchronous operation. + /// + /// The start slot (inclusive). + /// The start slot (inclusive and optional). + /// Returns a task that holds the asynchronous operation result and state. + Task>> GetBlocksAsync(ulong startSlot, ulong endSlot = 0); + + /// + /// Returns a list of confirmed blocks starting at the given slot. This is a synchronous operation. + /// + /// The start slot (inclusive). + /// The max number of blocks to return. + /// Returns an object that wraps the result along with possible errors with the request. + RequestResult> GetBlocksWithLimit(ulong startSlot, ulong limit); + + /// + /// Returns a list of confirmed blocks starting at the given slot. This is an asynchronous operation. + /// + /// The start slot (inclusive). + /// The max number of blocks to return. + /// Returns a task that holds the asynchronous operation result and state. + Task>> GetBlocksWithLimitAsync(ulong startSlot, ulong limit); + + /// + /// Returns the slot of the lowest confirmed block that has not been purged from the ledger. This is a synchronous operation. + /// + /// Returns an object that wraps the result along with possible errors with the request. + RequestResult GetFirstAvailableBlock(); + + /// + /// Returns the slot of the lowest confirmed block that has not been purged from the ledger. This is an asynchronous operation. + /// + /// Returns a task that holds the asynchronous operation result and state. + Task> GetFirstAvailableBlockAsync(); + + /// + /// Returns the current health of the node. + /// This method should return the strink 'ok' if the node is healthy, or the error code along with any information provided otherwise. + /// This is a synchronous operation. + /// + /// Returns an object that wraps the result along with possible errors with the request. + RequestResult GetHealth(); + + /// + /// Returns the current health of the node. + /// This method should return the strink 'ok' if the node is healthy, or the error code along with any information provided otherwise. + /// This is an asynchronous operation. + /// + /// Returns a task that holds the asynchronous operation result and state. + Task> GetHealthAsync(); + + /// + /// Returns the leader schedule for an epoch. This is a synchronous operation. + /// + /// Fetch the leader schedule for the epoch that corresponds to the provided slot. + /// If unspecified, the leader schedule for the current epoch is fetched. + /// Filter results for this validator only (base 58 encoded string and optional). + /// Returns an object that wraps the result along with possible errors with the request. + RequestResult>> GetLeaderSchedule(ulong slot = 0, string identity = null); + + /// + /// Returns the leader schedule for an epoch. This is an asynchronous operation. + /// + /// Fetch the leader schedule for the epoch that corresponds to the provided slot. + /// If unspecified, the leader schedule for the current epoch is fetched. + /// Filter results for this validator only (base 58 encoded string and optional). + /// Returns a task that holds the asynchronous operation result and state. + Task>>> GetLeaderScheduleAsync(ulong slot = 0, string identity = null); + + /// + /// Gets the block commitment of a certain block, identified by slot. + /// + /// The slot. + /// A task which may return a request result and block commitment information. + Task> GetBlockCommitmentAsync(ulong slot); + + /// + RequestResult GetBlockCommitment(ulong slot); + + /// + /// Gets the estimated production time for a certain block, identified by slot. + /// + /// The slot. + /// A task which may return a request result and block production time as Unix timestamp (seconds since Epoch). + Task> GetBlockTimeAsync(ulong slot); + + /// + RequestResult GetBlockTime(ulong slot); + + /// + /// Gets the current block height of the node. + /// + /// A task which may return a request result and a block height. + Task> GetBlockHeightAsync(); + + /// + RequestResult GetBlockHeight(); + + /// + /// Gets the cluster nodes. + /// + /// A task which may return a request result and information about the nodes participating in the cluster. + Task>> GetClusterNodesAsync(); + + /// + RequestResult> GetClusterNodes(); + + /// + /// Gets information about the current epoch. + /// + /// A task which may return a request result and information about the current epoch. + Task> GetEpochInfoAsync(); + + /// + RequestResult GetEpochInfo(); + + /// + /// Gets epoch schedule information from this cluster's genesis config. + /// + /// A task which may return a request result and epoch schedule information from this cluster's genesis config. + Task> GetEpochScheduleAsync(); + + /// + RequestResult GetEpochSchedule(); + + /// + /// Gets the fee calculator associated with the query blockhash, or null if the blockhash has expired. + /// + /// The blockhash to query, as base-58 encoded string. + /// A task which may return a request result and the fee calculator for the block. + Task>> GetFeeCalculatorForBlockhashAsync(string blockhash); + + /// + RequestResult> GetFeeCalculatorForBlockhash(string blockhash); + + /// + /// Gets the fee rate governor information from the root bank. + /// + /// A task which may return a request result and the fee rate governor. + Task>> GetFeeRateGovernorAsync(); + + /// + RequestResult> GetFeeRateGovernor(); + + /// + /// Gets a recent block hash from the ledger, a fee schedule that can be used to compute the + /// cost of submitting a transaction using it, and the last slot in which the blockhash will be valid. + /// + /// A task which may return a request result and information about fees. + Task>> GetFeesAsync(); + + /// + RequestResult> GetFees(); + + /// + /// Gets a recent block hash. + /// + /// A task which may return a request result and recent block hash. + Task>> GetRecentBlockHashAsync(); + + /// + RequestResult> GetRecentBlockHash(); + + /// + /// Gets the minimum balance required to make account rent exempt. + /// + /// The account data size. + /// A task which may return a request result and the rent exemption value. + Task> GetMinimumBalanceForRentExemptionAsync(long accountDataSize); + + /// + RequestResult GetMinimumBalanceForRentExemption(long accountDataSize); + + /// + /// Gets the genesis hash of the ledger. + /// + /// A task which may return a request result and a string representing the genesis hash. + Task> GetGenesisHashAsync(); + + /// + RequestResult GetGenesisHash(); + + /// + /// Gets the identity pubkey for the current node. + /// + /// A task which may return a request result and an object with the identity public key. + Task> GetIdentityAsync(); + + /// + RequestResult GetIdentity(); + + /// + /// Gets the current inflation governor. + /// + /// A task which may return a request result and an object representing the current inflation governor. + Task> GetInflationGovernorAsync(); + + /// + RequestResult GetInflationGovernor(); + + /// + /// Gets the specific inflation values for the current epoch. + /// + /// A task which may return a request result and an object representing the current inflation rate. + Task> GetInflationRateAsync(); + + /// + RequestResult GetInflationRate(); + + /// + /// Gets the inflation reward for a list of addresses for an epoch. + /// + /// The list of addresses to query, as base-58 encoded strings. + /// The epoch. + /// A task which may return a request result and a list of objects representing the inflation reward. + Task>> GetInflationRewardAsync(List addresses, ulong epoch = 0); + + /// + RequestResult> GetInflationReward(List addresses, ulong epoch = 0); + + /// + /// Gets the 20 largest accounts, by lamport balance. + /// + /// Filter results by account type. + /// A task which may return a request result and a list about the largest accounts. + /// Results may be cached up to two hours. + Task>>> GetLargestAccountsAsync(string filter); + + /// + RequestResult>> GetLargestAccounts(string filter); + + /// + /// Gets the maximum slot seen from retransmit stage. + /// + /// A task which may return a request result the maximum slot. + Task> GetMaxRetransmitSlotAsync(); + + /// + RequestResult GetMaxRetransmitSlot(); + + /// + /// Gets the maximum slot seen from after shred insert. + /// + /// A task which may return a request result the maximum slot. + Task> GetMaxShredInsertSlotAsync(); + + /// + RequestResult GetMaxShredInsertSlot(); + + /// + /// Gets the highest slot that the node has a snapshot for. + /// + /// A task which may return a request result the highest slot with a snapshot. + Task> GetSnapshotSlotAsync(); + + /// + RequestResult GetSnapshotSlot(); + + /// + /// Gets a list of recent performance samples. + /// + /// Unless searchTransactionHistory is included, this method only searches the recent status cache of signatures. + /// + /// + /// Maximum transaction signatures to return, between 1-720. Default is 720. + /// A task which may return a request result the signatures for the transactions. + Task>> GetRecentPerformanceSamplesAsync(ulong limit = 720); + + /// + RequestResult> GetRecentPerformanceSamples(ulong limit = 720); + + /// + /// Gets confirmed signatures for transactions involving the address. + /// + /// Unless searchTransactionHistory is included, this method only searches the recent status cache of signatures. + /// + /// + /// The account address as base-58 encoded string. + /// Maximum transaction signatures to return, between 1-1000. Default is 1000. + /// Start searching backwards from this transaction signature. + /// Search until this transaction signature, if found before limit is reached. + /// A task which may return a request result the signatures for the transactions. + Task>> GetSignaturesForAddressAsync(string accountPubKey, ulong limit = 1000, string before = "", string until = ""); + + /// + RequestResult> GetSignaturesForAddress(string accountPubKey, ulong limit = 1000, string before = "", string until = ""); + + /// + /// Gets the status of a list of signatures. + /// + /// Unless searchTransactionHistory is included, this method only searches the recent status cache of signatures. + /// + /// + /// The list of transactions to search status info for. + /// If the node should search for signatures in it's ledger cache. + /// A task which may return a request result the highest slot with a snapshot. + Task>>> GetSignatureStatusesAsync(List transactionHashes, + bool searchTransactionHistory = false); + + /// + RequestResult>> GetSignatureStatuses(List transactionHashes, + bool searchTransactionHistory = false); + + /// + /// Gets the current slot the node is processing + /// + /// A task which may return a request result the current slot. + Task> GetSlotAsync(); + + /// + RequestResult GetSlot(); + + /// + /// Gets the current slot leader. + /// + /// A task which may return a request result the slot leader. + Task> GetSlotLeaderAsync(); + + /// + RequestResult GetSlotLeader(); + + /// + /// Gets the slot leaders for a given slot range. + /// + /// The start slot. + /// The result limit. + /// A task which may return a request result and a list slot leaders. + Task>> GetSlotLeadersAsync(ulong start, ulong limit); + + /// + RequestResult> GetSlotLeaders(ulong start, ulong limit); + + /// + /// Gets the epoch activation information for a stake account. + /// + /// Public key of account to query, as base-58 encoded string + /// Epoch for which to calculate activation details. + /// A task which may return a request result and information about the stake activation. + Task> GetStakeActivationAsync(string publicKey, ulong epoch = 0); + + /// + RequestResult GetStakeActivation(string publicKey, ulong epoch = 0); + + /// + /// Gets information about the current supply. + /// + /// A task which may return a request result and information about the supply. + Task>> GetSupplyAsync(); + + /// + RequestResult> GetSupply(); + + /// + /// Gets the token balance of an SPL Token account. + /// + /// Public key of Token account to query, as base-58 encoded string. + /// A task which may return a request result and information about the token account balance. + Task>> GetTokenAccountBalanceAsync(string splTokenAccountPublicKey); + + /// + RequestResult> GetTokenAccountBalance(string splTokenAccountPublicKey); + + /// + /// Gets all SPL Token accounts by approved delegate. + /// + /// Public key of account owner query, as base-58 encoded string. + /// Public key of the specific token Mint to limit accounts to, as base-58 encoded string. + /// Public key of the Token program ID that owns the accounts, as base-58 encoded string. + /// A task which may return a request result and information about token accounts by delegate + Task>>> GetTokenAccountsByDelegateAsync( + string ownerPubKey, string tokenMintPubKey = "", string tokenProgramId = ""); + + /// + RequestResult>> GetTokenAccountsByDelegate( + string ownerPubKey, string tokenMintPubKey = "", string tokenProgramId = ""); + + /// + /// Gets all SPL Token accounts by token owner. + /// + /// Public key of account owner query, as base-58 encoded string. + /// Public key of the specific token Mint to limit accounts to, as base-58 encoded string. + /// Public key of the Token program ID that owns the accounts, as base-58 encoded string. + /// A task which may return a request result and information about token accounts by owner. + Task>>> GetTokenAccountsByOwnerAsync( + string ownerPubKey, string tokenMintPubKey = "", string tokenProgramId = ""); + + /// + RequestResult>> GetTokenAccountsByOwner( + string ownerPubKey, string tokenMintPubKey = "", string tokenProgramId = ""); + + /// + /// Gets the 20 largest token accounts of a particular SPL Token. + /// + /// Public key of Token Mint to query, as base-58 encoded string. + /// A task which may return a request result and information about largest token accounts. + Task>>> GetTokenLargestAccountsAsync(string tokenMintPubKey); + + /// + RequestResult>> GetTokenLargestAccounts(string tokenMintPubKey); + + /// + /// Get the token supply of an SPL Token type. + /// + /// Public key of Token Mint to query, as base-58 encoded string. + /// A task which may return a request result and information about the supply. + Task>> GetTokenSupplyAsync(string tokenMintPubKey); + + /// + RequestResult> GetTokenSupply(string tokenMintPubKey); + + /// + /// Gets the total transaction count of the ledger. + /// + /// A task which may return a request result and information about the transaction count. + Task> GetTransactionCountAsync(); + + /// + RequestResult GetTransactionCount(); + + + /// + /// Gets the current node's software version info. + /// + /// A task which may return a request result and information about the current node's software. + Task> GetVersionAsync(); + + /// + RequestResult GetVersion(); + + /// + /// Gets the account info and associated stake for all voting accounts in the current bank. + /// + /// A task which may return a request result and information about the vote accounts. + Task> GetVoteAccountsAsync(); + + /// + RequestResult GetVoteAccounts(); + + /// + /// Gets the lowest slot that the node has information about in its ledger. + /// + /// This value may decrease over time if a node is configured to purging data. + /// + /// + /// A task which may return a request result and the minimum slot available. + Task> GetMinimumLedgerSlotAsync(); + + /// + RequestResult GetMinimumLedgerSlot(); + + /// + /// Requests an airdrop to the passed pubKey of the passed lamports amount. + /// + /// The commitment parameter is optional, the default is used. + /// + /// + /// The public key of to receive the airdrop. + /// The amount of lamports to request. + /// The block commitment used to retrieve block hashes and verify success. + /// A task which may return a request result and the transaction signature of the airdrop, as base-58 encoded string.. + Task> RequestAirdropAsync(string pubKey, ulong lamports, + Commitment commitment = Commitment.Finalized); + + /// + RequestResult RequestAirdrop(string pubKey, ulong lamports, + Commitment commitment = Commitment.Finalized); + + /// + /// Sends a transaction. + /// + /// The signed transaction as base-58 or base-64 encoded string. + /// The encoding of the transaction. + /// + /// A task which may return a request result and the first transaction signature embedded in the transaction, as base-58 encoded string. + /// + Task> SendTransactionAsync(string transaction, + BinaryEncoding encoding = BinaryEncoding.Base64); + + /// + RequestResult SendTransaction(string transaction, BinaryEncoding encoding = BinaryEncoding.Base64); + + /// + /// Sends a transaction. + /// + /// The signed transaction as byte array. + /// + /// A task which may return a request result and the first transaction signature embedded in the transaction, as base-58 encoded string. + /// + RequestResult SendTransaction(byte[] transaction); + + /// + /// Simulate sending a transaction. + /// + /// The signed transaction as a byte array. + /// The encoding of the transaction. + /// + /// A task which may return a request result and the transaction status. + /// + Task>> SimulateTransactionAsync(string transaction, + BinaryEncoding encoding = BinaryEncoding.Base64); + + /// + RequestResult> SimulateTransaction(string transaction, + BinaryEncoding encoding = BinaryEncoding.Base64); + + /// + /// Simulate sending a transaction. + /// + /// The signed transaction as a byte array. + /// + /// A task which may return a request result and the transaction status. + /// + RequestResult> SimulateTransaction(byte[] transaction); + } } \ No newline at end of file diff --git a/src/Solnet.Rpc/IStreamingRpcClient.cs b/src/Solnet.Rpc/IStreamingRpcClient.cs index a7b15cb2..89179d08 100644 --- a/src/Solnet.Rpc/IStreamingRpcClient.cs +++ b/src/Solnet.Rpc/IStreamingRpcClient.cs @@ -1,148 +1,148 @@ -using Solnet.Rpc.Core.Sockets; -using Solnet.Rpc.Messages; -using Solnet.Rpc.Models; -using Solnet.Rpc.Types; -using System; -using System.Threading.Tasks; - -namespace Solnet.Rpc -{ - /// - /// Represents the streaming RPC client for the solana API. - /// - public interface IStreamingRpcClient - { - /// - /// The address this client connects to. - /// - Uri NodeAddress { get; } - - /// - /// Subscribes asynchronously to AccountInfo notifications. - /// - /// The public key of the account. - /// The callback to handle data notifications. - /// The task object representing the asynchronous operation. - Task SubscribeAccountInfoAsync(string pubkey, Action> callback); - - /// - /// Subscribes to the AccountInfo. This is a synchronous and blocking function. - /// - /// The public key of the account. - /// The callback to handle data notifications. - /// Returns an object representing the state of the subscription. - SubscriptionState SubscribeAccountInfo(string pubkey, Action> callback); - - /// - /// Subscribes asynchronously to the logs notifications that mention a given public key. - /// - /// The public key to filter by mention. - /// The callback to handle data notifications. - /// The task object representing the asynchronous operation. - Task SubscribeLogInfoAsync(string pubkey, Action> callback); - - /// - /// Subscribes asynchronously to the logs notifications. - /// - /// The filter mechanism. - /// The callback to handle data notifications. - /// The task object representing the asynchronous operation. - Task SubscribeLogInfoAsync(LogsSubscriptionType subscriptionType, Action> callback); - - /// - /// Subscribes to the logs notifications that mention a given public key. This is a synchronous and blocking function. - /// - /// The public key to filter by mention. - /// The callback to handle data notifications. - /// Returns an object representing the state of the subscription. - SubscriptionState SubscribeLogInfo(string pubkey, Action> callback); - - /// - /// Subscribes to the logs notifications. This is a synchronous and blocking function. - /// - /// The filter mechanism. - /// The callback to handle data notifications. - /// Returns an object representing the state of the subscription. - SubscriptionState SubscribeLogInfo(LogsSubscriptionType subscriptionType, Action> callback); - - /// - /// Subscribes asynchronously to a transaction signature to receive notification when the transaction is confirmed. - /// - /// The transaction signature. - /// The callback to handle data notifications. - /// The task object representing the asynchronous operation. - Task SubscribeSignatureAsync(string transactionSignature, Action> callback); - - /// - /// Subscribes to a transaction signature to receive notification when the transaction is confirmed. This is a synchronous and blocking function. - /// - /// The transaction signature. - /// The callback to handle data notifications. - /// Returns an object representing the state of the subscription. - SubscriptionState SubscribeSignature(string transactionSignature, Action> callback); - - /// - /// Subscribes asynchronously to changes to a given program account data. - /// - /// The program pubkey. - /// The callback to handle data notifications. - /// The task object representing the asynchronous operation. - Task SubscribeProgramAsync(string programPubkey, Action> callback); - - /// - /// Subscribes to changes to a given program account data. This is a synchronous and blocking function. - /// - /// The program pubkey. - /// The callback to handle data notifications. - /// Returns an object representing the state of the subscription. - SubscriptionState SubscribeProgram(string programPubkey, Action> callback); - - /// - /// Subscribes asynchronously to receive notifications anytime a slot is processed by the validator. - /// - /// The callback to handle data notifications. - /// The task object representing the asynchronous operation. - Task SubscribeSlotInfoAsync(Action callback); - - /// - /// Subscribes to receive notifications anytime a slot is processed by the validator. This is a synchronous and blocking function. - /// - /// The callback to handle data notifications. - /// Returns an object representing the state of the subscription. - SubscriptionState SubscribeSlotInfo(Action callback); - - - /// - /// Subscribes asynchronously to receive notifications anytime a new root is set by the validator. - /// - /// The callback to handle data notifications. - /// The task object representing the asynchronous operation. - Task SubscribeRootAsync(Action callback); - - /// - /// Subscribes to receive notifications anytime a new root is set by the validator. This is a synchronous and blocking function. - /// - /// The callback to handle data notifications. - /// Returns an object representing the state of the subscription. - SubscriptionState SubscribeRoot(Action callback); - - /// - /// Asynchronously unsubscribes from a given subscription using the state object. - /// - /// The subscription state object. - /// The task object representing the asynchronous operation. - Task UnsubscribeAsync(SubscriptionState subscription); - - /// - /// Unsubscribes from a given subscription using the state object. This is a synchronous and blocking function. - /// - /// The subscription state object. - void Unsubscribe(SubscriptionState subscription); - - /// - /// Asynchronously initializes the client connection asynchronously. - /// - /// The task object representing the asynchronous operation. - Task Init(); - } +using Solnet.Rpc.Core.Sockets; +using Solnet.Rpc.Messages; +using Solnet.Rpc.Models; +using Solnet.Rpc.Types; +using System; +using System.Threading.Tasks; + +namespace Solnet.Rpc +{ + /// + /// Represents the streaming RPC client for the solana API. + /// + public interface IStreamingRpcClient + { + /// + /// The address this client connects to. + /// + Uri NodeAddress { get; } + + /// + /// Subscribes asynchronously to AccountInfo notifications. + /// + /// The public key of the account. + /// The callback to handle data notifications. + /// The task object representing the asynchronous operation. + Task SubscribeAccountInfoAsync(string pubkey, Action> callback); + + /// + /// Subscribes to the AccountInfo. This is a synchronous and blocking function. + /// + /// The public key of the account. + /// The callback to handle data notifications. + /// Returns an object representing the state of the subscription. + SubscriptionState SubscribeAccountInfo(string pubkey, Action> callback); + + /// + /// Subscribes asynchronously to the logs notifications that mention a given public key. + /// + /// The public key to filter by mention. + /// The callback to handle data notifications. + /// The task object representing the asynchronous operation. + Task SubscribeLogInfoAsync(string pubkey, Action> callback); + + /// + /// Subscribes asynchronously to the logs notifications. + /// + /// The filter mechanism. + /// The callback to handle data notifications. + /// The task object representing the asynchronous operation. + Task SubscribeLogInfoAsync(LogsSubscriptionType subscriptionType, Action> callback); + + /// + /// Subscribes to the logs notifications that mention a given public key. This is a synchronous and blocking function. + /// + /// The public key to filter by mention. + /// The callback to handle data notifications. + /// Returns an object representing the state of the subscription. + SubscriptionState SubscribeLogInfo(string pubkey, Action> callback); + + /// + /// Subscribes to the logs notifications. This is a synchronous and blocking function. + /// + /// The filter mechanism. + /// The callback to handle data notifications. + /// Returns an object representing the state of the subscription. + SubscriptionState SubscribeLogInfo(LogsSubscriptionType subscriptionType, Action> callback); + + /// + /// Subscribes asynchronously to a transaction signature to receive notification when the transaction is confirmed. + /// + /// The transaction signature. + /// The callback to handle data notifications. + /// The task object representing the asynchronous operation. + Task SubscribeSignatureAsync(string transactionSignature, Action> callback); + + /// + /// Subscribes to a transaction signature to receive notification when the transaction is confirmed. This is a synchronous and blocking function. + /// + /// The transaction signature. + /// The callback to handle data notifications. + /// Returns an object representing the state of the subscription. + SubscriptionState SubscribeSignature(string transactionSignature, Action> callback); + + /// + /// Subscribes asynchronously to changes to a given program account data. + /// + /// The program pubkey. + /// The callback to handle data notifications. + /// The task object representing the asynchronous operation. + Task SubscribeProgramAsync(string programPubkey, Action> callback); + + /// + /// Subscribes to changes to a given program account data. This is a synchronous and blocking function. + /// + /// The program pubkey. + /// The callback to handle data notifications. + /// Returns an object representing the state of the subscription. + SubscriptionState SubscribeProgram(string programPubkey, Action> callback); + + /// + /// Subscribes asynchronously to receive notifications anytime a slot is processed by the validator. + /// + /// The callback to handle data notifications. + /// The task object representing the asynchronous operation. + Task SubscribeSlotInfoAsync(Action callback); + + /// + /// Subscribes to receive notifications anytime a slot is processed by the validator. This is a synchronous and blocking function. + /// + /// The callback to handle data notifications. + /// Returns an object representing the state of the subscription. + SubscriptionState SubscribeSlotInfo(Action callback); + + + /// + /// Subscribes asynchronously to receive notifications anytime a new root is set by the validator. + /// + /// The callback to handle data notifications. + /// The task object representing the asynchronous operation. + Task SubscribeRootAsync(Action callback); + + /// + /// Subscribes to receive notifications anytime a new root is set by the validator. This is a synchronous and blocking function. + /// + /// The callback to handle data notifications. + /// Returns an object representing the state of the subscription. + SubscriptionState SubscribeRoot(Action callback); + + /// + /// Asynchronously unsubscribes from a given subscription using the state object. + /// + /// The subscription state object. + /// The task object representing the asynchronous operation. + Task UnsubscribeAsync(SubscriptionState subscription); + + /// + /// Unsubscribes from a given subscription using the state object. This is a synchronous and blocking function. + /// + /// The subscription state object. + void Unsubscribe(SubscriptionState subscription); + + /// + /// Asynchronously initializes the client connection asynchronously. + /// + /// The task object representing the asynchronous operation. + Task Init(); + } } \ No newline at end of file diff --git a/src/Solnet.Rpc/Messages/JsonRpcBase.cs b/src/Solnet.Rpc/Messages/JsonRpcBase.cs index 0b2e2406..ca047b64 100644 --- a/src/Solnet.Rpc/Messages/JsonRpcBase.cs +++ b/src/Solnet.Rpc/Messages/JsonRpcBase.cs @@ -1,9 +1,9 @@ -namespace Solnet.Rpc.Messages -{ - public class JsonRpcBase - { - public string Jsonrpc { get; protected set; } - - public int Id { get; set; } - } +namespace Solnet.Rpc.Messages +{ + public class JsonRpcBase + { + public string Jsonrpc { get; protected set; } + + public int Id { get; set; } + } } \ No newline at end of file diff --git a/src/Solnet.Rpc/Messages/JsonRpcRequest.cs b/src/Solnet.Rpc/Messages/JsonRpcRequest.cs index 18957412..a04b3979 100644 --- a/src/Solnet.Rpc/Messages/JsonRpcRequest.cs +++ b/src/Solnet.Rpc/Messages/JsonRpcRequest.cs @@ -1,21 +1,21 @@ -using System.Collections.Generic; -using System.Text.Json.Serialization; - -namespace Solnet.Rpc.Messages -{ - public class JsonRpcRequest : JsonRpcBase - { - public string Method { get; } - - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public IList Params { get; } - - internal JsonRpcRequest(int id, string method, IList parameters) - { - Params = parameters; - Method = method; - Id = id; - Jsonrpc = "2.0"; - } - } +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Solnet.Rpc.Messages +{ + public class JsonRpcRequest : JsonRpcBase + { + public string Method { get; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public IList Params { get; } + + internal JsonRpcRequest(int id, string method, IList parameters) + { + Params = parameters; + Method = method; + Id = id; + Jsonrpc = "2.0"; + } + } } \ No newline at end of file diff --git a/src/Solnet.Rpc/Messages/JsonRpcResponse.cs b/src/Solnet.Rpc/Messages/JsonRpcResponse.cs index 2c667715..206cbb89 100644 --- a/src/Solnet.Rpc/Messages/JsonRpcResponse.cs +++ b/src/Solnet.Rpc/Messages/JsonRpcResponse.cs @@ -1,36 +1,36 @@ -using System.Collections.Generic; -using System.Text.Json.Serialization; - -namespace Solnet.Rpc.Messages -{ - public class JsonRpcResponse : JsonRpcBase - { - public T Result { get; set; } - } - - public class JsonRpcErrorResponse : JsonRpcBase - { - public ErrorContent Error { get; set; } - } - - public class ErrorContent - { - public int Code { get; set; } - public string Message { get; set; } - - [JsonExtensionData] - public Dictionary Data { get; set; } - } - - public class ContextObj - { - public ulong Slot { get; set; } - } - - public class ResponseValue - { - public ContextObj Context { get; set; } - - public T Value { get; set; } - } +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Solnet.Rpc.Messages +{ + public class JsonRpcResponse : JsonRpcBase + { + public T Result { get; set; } + } + + public class JsonRpcErrorResponse : JsonRpcBase + { + public ErrorContent Error { get; set; } + } + + public class ErrorContent + { + public int Code { get; set; } + public string Message { get; set; } + + [JsonExtensionData] + public Dictionary Data { get; set; } + } + + public class ContextObj + { + public ulong Slot { get; set; } + } + + public class ResponseValue + { + public ContextObj Context { get; set; } + + public T Value { get; set; } + } } \ No newline at end of file diff --git a/src/Solnet.Rpc/Messages/JsonRpcStreamResponse.cs b/src/Solnet.Rpc/Messages/JsonRpcStreamResponse.cs index 0aa9060f..36ed7ecf 100644 --- a/src/Solnet.Rpc/Messages/JsonRpcStreamResponse.cs +++ b/src/Solnet.Rpc/Messages/JsonRpcStreamResponse.cs @@ -1,9 +1,9 @@ -namespace Solnet.Rpc.Messages -{ - public class JsonRpcStreamResponse - { - public T Result { get; set; } - - public int Subscription { get; set; } - } +namespace Solnet.Rpc.Messages +{ + public class JsonRpcStreamResponse + { + public T Result { get; set; } + + public int Subscription { get; set; } + } } \ No newline at end of file diff --git a/src/Solnet.Rpc/Models/AccountData.cs b/src/Solnet.Rpc/Models/AccountData.cs index 980a9eb0..badc1000 100644 --- a/src/Solnet.Rpc/Models/AccountData.cs +++ b/src/Solnet.Rpc/Models/AccountData.cs @@ -1,144 +1,144 @@ -// ReSharper disable ClassNeverInstantiated.Global -using System; - -namespace Solnet.Rpc.Models -{ - /// - /// Represents the account info for a given token account. - /// - public class TokenAccountInfo : AccountInfoBase - { - /// - /// The parsed token account data field. - /// - public TokenAccountData Data { get; set; } - } - - /// - /// Represents the details of the info field of a token account. - /// - public class TokenAccountInfoDetails - { - /// - /// The token balance data. - /// - public TokenBalance TokenAmount { get; set; } - - /// - /// A base-58 encoded public key of the delegate. - /// - public string Delegate { get; set; } - - /// - /// The delegated amount of tokens. - /// - public ulong DelegatedAmount { get; set; } - - /// - /// The account's state. - /// - public string State { get; set; } - - /// - /// If the account is a native token account. - /// - public bool IsNative { get; set; } - - /// - /// A base-58 encoded public key of the token's mint. - /// - public string Mint { get; set; } - - /// - /// A base-58 encoded public key of the program this account as been assigned to. - /// - public string Owner { get; set; } - } - - /// - /// Represents the parsed account data, as available by the program-specific state parser. - /// - public class ParsedTokenAccountData - { - /// - /// The type of account. - /// - public string Type { get; set; } - - /// - /// The token account info, containing account balances, delegation and ownership info. - /// - public TokenAccountInfoDetails Info { get; set; } - } - - /// - /// Represents a token account's data. - /// - public class TokenAccountData - { - /// - /// The program responsible for the account data. - /// - public string Program { get; set; } - - /// - /// The parsed account data, as available by the program-specific state parser. - /// - public ParsedTokenAccountData Parsed { get; set; } - } - - /// - /// Represents a large token account. - /// - public class LargeTokenAccount : TokenBalance - { - /// - /// The address of the token account. - /// - public string Address { get; set; } - } - - /// - /// Represents a large account. - /// - public class LargeAccount - { - /// - /// The lamports balance of the account. - /// - public ulong Lamports { get; set; } - - - /// - /// The address of the token account. - /// - public string Address { get; set; } - } - - /// - /// Represents the token balance of an account. - /// - public class TokenBalance - { - /// - /// The raw token account balance without decimals. - /// - public string Amount { get; set; } - - /// - /// The number of base 10 digits to the right of the decimal place. - /// - public int Decimals { get; set; } - - /// - /// The token account balance, using mint-prescribed decimals. DEPRECATED. - /// - [Obsolete("UiAmount is deprecated, please use UiAmountString instead.")] - public decimal? UiAmount { get; set; } - - /// - /// The token account balance as a string, using mint-prescribed decimals. - /// - public string UiAmountString { get; set; } - } +// ReSharper disable ClassNeverInstantiated.Global +using System; + +namespace Solnet.Rpc.Models +{ + /// + /// Represents the account info for a given token account. + /// + public class TokenAccountInfo : AccountInfoBase + { + /// + /// The parsed token account data field. + /// + public TokenAccountData Data { get; set; } + } + + /// + /// Represents the details of the info field of a token account. + /// + public class TokenAccountInfoDetails + { + /// + /// The token balance data. + /// + public TokenBalance TokenAmount { get; set; } + + /// + /// A base-58 encoded public key of the delegate. + /// + public string Delegate { get; set; } + + /// + /// The delegated amount of tokens. + /// + public ulong DelegatedAmount { get; set; } + + /// + /// The account's state. + /// + public string State { get; set; } + + /// + /// If the account is a native token account. + /// + public bool IsNative { get; set; } + + /// + /// A base-58 encoded public key of the token's mint. + /// + public string Mint { get; set; } + + /// + /// A base-58 encoded public key of the program this account as been assigned to. + /// + public string Owner { get; set; } + } + + /// + /// Represents the parsed account data, as available by the program-specific state parser. + /// + public class ParsedTokenAccountData + { + /// + /// The type of account. + /// + public string Type { get; set; } + + /// + /// The token account info, containing account balances, delegation and ownership info. + /// + public TokenAccountInfoDetails Info { get; set; } + } + + /// + /// Represents a token account's data. + /// + public class TokenAccountData + { + /// + /// The program responsible for the account data. + /// + public string Program { get; set; } + + /// + /// The parsed account data, as available by the program-specific state parser. + /// + public ParsedTokenAccountData Parsed { get; set; } + } + + /// + /// Represents a large token account. + /// + public class LargeTokenAccount : TokenBalance + { + /// + /// The address of the token account. + /// + public string Address { get; set; } + } + + /// + /// Represents a large account. + /// + public class LargeAccount + { + /// + /// The lamports balance of the account. + /// + public ulong Lamports { get; set; } + + + /// + /// The address of the token account. + /// + public string Address { get; set; } + } + + /// + /// Represents the token balance of an account. + /// + public class TokenBalance + { + /// + /// The raw token account balance without decimals. + /// + public string Amount { get; set; } + + /// + /// The number of base 10 digits to the right of the decimal place. + /// + public int Decimals { get; set; } + + /// + /// The token account balance, using mint-prescribed decimals. DEPRECATED. + /// + [Obsolete("UiAmount is deprecated, please use UiAmountString instead.")] + public decimal? UiAmount { get; set; } + + /// + /// The token account balance as a string, using mint-prescribed decimals. + /// + public string UiAmountString { get; set; } + } } \ No newline at end of file diff --git a/src/Solnet.Rpc/Models/AccountInfo.cs b/src/Solnet.Rpc/Models/AccountInfo.cs index f381e6db..10ff80d6 100644 --- a/src/Solnet.Rpc/Models/AccountInfo.cs +++ b/src/Solnet.Rpc/Models/AccountInfo.cs @@ -1,68 +1,68 @@ -// ReSharper disable UnusedAutoPropertyAccessor.Global -// ReSharper disable ClassNeverInstantiated.Global -using System.Collections.Generic; -using System.Text.Json.Serialization; - -namespace Solnet.Rpc.Models -{ - /// - /// The base class of the account info, to be subclassed for token a account info classes. - /// - public class AccountInfoBase - { - /// - /// The lamports balance of the account. - /// - public ulong Lamports { get; set; } - - /// - /// The account owner. - /// - /// This value could be another regular address or a program. - /// - /// - public string Owner { get; set; } - - /// - /// Indicates whether the account contains a program (and is strictly read-only). - /// - public bool Executable { get; set; } - - /// - /// The epoch at which the account will next owe rent. - /// - public ulong RentEpoch { get; set; } - } - - /// - /// Represents the account info. - /// - public class AccountInfo : AccountInfoBase - { - /// - /// The actual account data. - /// - /// This field should contain two values: first value is the data, the second one is the encoding - should always read base64. - /// - /// - public List Data { get; set; } - } - - /// - /// Represents the tuple account key and account data. - /// - public class AccountKeyPair - { - /// - /// The account info. - /// - public AccountInfo Account { get; set; } - - /// - /// A base-58 encoded public key representing the account's public key. - /// - [JsonPropertyName("pubkey")] - public string PublicKey { get; set; } - } - +// ReSharper disable UnusedAutoPropertyAccessor.Global +// ReSharper disable ClassNeverInstantiated.Global +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Solnet.Rpc.Models +{ + /// + /// The base class of the account info, to be subclassed for token a account info classes. + /// + public class AccountInfoBase + { + /// + /// The lamports balance of the account. + /// + public ulong Lamports { get; set; } + + /// + /// The account owner. + /// + /// This value could be another regular address or a program. + /// + /// + public string Owner { get; set; } + + /// + /// Indicates whether the account contains a program (and is strictly read-only). + /// + public bool Executable { get; set; } + + /// + /// The epoch at which the account will next owe rent. + /// + public ulong RentEpoch { get; set; } + } + + /// + /// Represents the account info. + /// + public class AccountInfo : AccountInfoBase + { + /// + /// The actual account data. + /// + /// This field should contain two values: first value is the data, the second one is the encoding - should always read base64. + /// + /// + public List Data { get; set; } + } + + /// + /// Represents the tuple account key and account data. + /// + public class AccountKeyPair + { + /// + /// The account info. + /// + public AccountInfo Account { get; set; } + + /// + /// A base-58 encoded public key representing the account's public key. + /// + [JsonPropertyName("pubkey")] + public string PublicKey { get; set; } + } + } \ No newline at end of file diff --git a/src/Solnet.Rpc/Models/AccountKeysList.cs b/src/Solnet.Rpc/Models/AccountKeysList.cs index 53c36b67..0d5b74da 100644 --- a/src/Solnet.Rpc/Models/AccountKeysList.cs +++ b/src/Solnet.Rpc/Models/AccountKeysList.cs @@ -1,74 +1,74 @@ -using System; -using System.Collections.Generic; - -namespace Solnet.Rpc.Models -{ - /// - /// A wrapper around a dictionary of key-value pairs of public-keys - account metas to be used - /// during transaction building. It checks for differences in account meta when adding to the dictionary - /// and sorts the underlying list of values in the dictionary according to - /// so they are ordered and compatible with Solana's transaction anatomy. - /// - internal class AccountKeysList - { - /// - /// The dictionary with key-value pairs of public keys - account metas. - /// - private readonly Dictionary _accounts; - - /// - /// Get the values of the accounts dictionary as a list. - /// - internal IList AccountList - { - get - { - var list = new List(_accounts.Values); - list.Sort(AccountMetaExtensions.Compare); - return list; - } - } - - /// - /// Initialize the account keys list for use within transaction building. - /// - internal AccountKeysList() - { - _accounts = new Dictionary(); - } - - /// - /// Add a list of account metas to the dictionary with key-value pairs of public keys - account metas. - /// - /// The account meta to add. - /// Throws exception when account meta is already present and couldn't overwrite. - internal void Add(AccountMeta accountMeta) - { - if (_accounts.ContainsKey(accountMeta.GetPublicKey)) - { - var ok = _accounts.TryGetValue(accountMeta.GetPublicKey, out var account); - if (!ok) throw new Exception("account meta already exists but could not overwrite"); - if (accountMeta.Writable && !account.Writable) - { - _accounts.Add(accountMeta.GetPublicKey, accountMeta); - } - } - else - { - _accounts.Add(accountMeta.GetPublicKey, accountMeta); - } - } - - /// - /// Add a list of account metas to the dictionary with key-value pairs of public keys - account metas. - /// - /// The account meta to add. - internal void Add(IEnumerable accountMetas) - { - foreach (var accountMeta in accountMetas) - { - Add(accountMeta); - } - } - } +using System; +using System.Collections.Generic; + +namespace Solnet.Rpc.Models +{ + /// + /// A wrapper around a dictionary of key-value pairs of public-keys - account metas to be used + /// during transaction building. It checks for differences in account meta when adding to the dictionary + /// and sorts the underlying list of values in the dictionary according to + /// so they are ordered and compatible with Solana's transaction anatomy. + /// + internal class AccountKeysList + { + /// + /// The dictionary with key-value pairs of public keys - account metas. + /// + private readonly Dictionary _accounts; + + /// + /// Get the values of the accounts dictionary as a list. + /// + internal IList AccountList + { + get + { + var list = new List(_accounts.Values); + list.Sort(AccountMetaExtensions.Compare); + return list; + } + } + + /// + /// Initialize the account keys list for use within transaction building. + /// + internal AccountKeysList() + { + _accounts = new Dictionary(); + } + + /// + /// Add a list of account metas to the dictionary with key-value pairs of public keys - account metas. + /// + /// The account meta to add. + /// Throws exception when account meta is already present and couldn't overwrite. + internal void Add(AccountMeta accountMeta) + { + if (_accounts.ContainsKey(accountMeta.GetPublicKey)) + { + var ok = _accounts.TryGetValue(accountMeta.GetPublicKey, out var account); + if (!ok) throw new Exception("account meta already exists but could not overwrite"); + if (accountMeta.Writable && !account.Writable) + { + _accounts.Add(accountMeta.GetPublicKey, accountMeta); + } + } + else + { + _accounts.Add(accountMeta.GetPublicKey, accountMeta); + } + } + + /// + /// Add a list of account metas to the dictionary with key-value pairs of public keys - account metas. + /// + /// The account meta to add. + internal void Add(IEnumerable accountMetas) + { + foreach (var accountMeta in accountMetas) + { + Add(accountMeta); + } + } + } } \ No newline at end of file diff --git a/src/Solnet.Rpc/Models/AccountMeta.cs b/src/Solnet.Rpc/Models/AccountMeta.cs index f6c678d7..230ecf84 100644 --- a/src/Solnet.Rpc/Models/AccountMeta.cs +++ b/src/Solnet.Rpc/Models/AccountMeta.cs @@ -1,76 +1,76 @@ -using NBitcoin.DataEncoders; - -namespace Solnet.Rpc.Models -{ - /// - /// Implements the account meta logic, which defines if an account represented by public key is a signer, a writable account or both. - /// - public class AccountMeta - { - /// - /// The base58 encoder instance. - /// - private static readonly Base58Encoder Encoder = new(); - - /// - /// The public key. - /// - internal byte[] PublicKey; - - /// - /// A boolean which defines if the account is a signer account. - /// - internal bool Signer { get; } - - /// - /// A boolean which defines if the account is a writable account. - /// - internal bool Writable { get; } - - /// - /// Initialize the account meta with the passed public key, and booleans defining if it is a signer and/or writable account. - /// - /// The account's public key. - /// If the account is a signer. - /// If the account is writable. - public AccountMeta(byte[] publicKey, bool isSigner, bool isWritable) - { - PublicKey = publicKey; - Signer = isSigner; - Writable = isWritable; - } - - - /// - /// Get the public key encoded as base58. - /// - public string GetPublicKey => Encoder.EncodeData(PublicKey); - } - - /// - /// Implements extensions to . - /// - internal static class AccountMetaExtensions - { - /// - /// Compares two objects. - /// - /// The base of the comparison. - /// The object to compare the base to. - /// - /// Returns 0 if the objects are equal in terms of Signing and Writing, - /// -1 if the base of the comparison is something the other is not, otherwise 1. - /// - internal static int Compare(AccountMeta am1, AccountMeta am2) - { - var cmpSigner = am1.Signer == am2.Signer ? 0 : am1.Signer ? -1 : 1; - if (cmpSigner != 0) - { - return cmpSigner; - } - - var cmpWritable = am1.Writable == am2.Writable ? 0 : am1.Writable ? -1 : 1; - return cmpWritable != 0 ? cmpWritable : 0; - } - } +using NBitcoin.DataEncoders; + +namespace Solnet.Rpc.Models +{ + /// + /// Implements the account meta logic, which defines if an account represented by public key is a signer, a writable account or both. + /// + public class AccountMeta + { + /// + /// The base58 encoder instance. + /// + private static readonly Base58Encoder Encoder = new(); + + /// + /// The public key. + /// + internal byte[] PublicKey; + + /// + /// A boolean which defines if the account is a signer account. + /// + internal bool Signer { get; } + + /// + /// A boolean which defines if the account is a writable account. + /// + internal bool Writable { get; } + + /// + /// Initialize the account meta with the passed public key, and booleans defining if it is a signer and/or writable account. + /// + /// The account's public key. + /// If the account is a signer. + /// If the account is writable. + public AccountMeta(byte[] publicKey, bool isSigner, bool isWritable) + { + PublicKey = publicKey; + Signer = isSigner; + Writable = isWritable; + } + + + /// + /// Get the public key encoded as base58. + /// + public string GetPublicKey => Encoder.EncodeData(PublicKey); + } + + /// + /// Implements extensions to . + /// + internal static class AccountMetaExtensions + { + /// + /// Compares two objects. + /// + /// The base of the comparison. + /// The object to compare the base to. + /// + /// Returns 0 if the objects are equal in terms of Signing and Writing, + /// -1 if the base of the comparison is something the other is not, otherwise 1. + /// + internal static int Compare(AccountMeta am1, AccountMeta am2) + { + var cmpSigner = am1.Signer == am2.Signer ? 0 : am1.Signer ? -1 : 1; + if (cmpSigner != 0) + { + return cmpSigner; + } + + var cmpWritable = am1.Writable == am2.Writable ? 0 : am1.Writable ? -1 : 1; + return cmpWritable != 0 ? cmpWritable : 0; + } + } } \ No newline at end of file diff --git a/src/Solnet.Rpc/Models/Block.cs b/src/Solnet.Rpc/Models/Block.cs index 39c6e7c0..f762dfce 100644 --- a/src/Solnet.Rpc/Models/Block.cs +++ b/src/Solnet.Rpc/Models/Block.cs @@ -1,364 +1,364 @@ -// ReSharper disable UnusedAutoPropertyAccessor.Global -// ReSharper disable ClassNeverInstantiated.Global - -using System.Text.Json.Serialization; - -namespace Solnet.Rpc.Models -{ - /// - /// Represents the block info. - /// - public class BlockInfo - { - /// - /// Estimated block production time. - /// - public long BlockTime { get; set; } - - /// - /// A base-58 encoded public key representing the block hash. - /// - public string Blockhash { get; set; } - - /// - /// A base-58 encoded public key representing the block hash of this block's parent. - /// - /// If the parent block is no longer available due to ledger cleanup, this field will return - /// '11111111111111111111111111111111' - /// - /// - public string PreviousBlockhash { get; set; } - - /// - /// The slot index of this block's parent. - /// - public ulong ParentSlot { get; set; } - - /// - /// The number of blocks beneath this block. - /// - public long BlockHeight { get; set; } - - /// - /// The rewards for this given block. - /// - public RewardInfo[] Rewards { get; set; } - - /// - /// Collection of transactions and their metadata within this block. - /// - public TransactionMetaInfo[] Transactions { get; set; } - } - - /// - /// Represents the transaction, metadata and its containing slot. - /// - public class TransactionMetaSlotInfo : TransactionMetaInfo - { - /// - /// The slot this transaction was processed in. - /// - public ulong Slot { get; set; } - - /// - /// Estimated block production time. - /// - public long? BlockTime { get; set; } - } - - - /// - /// Represents the tuple transaction and metadata. - /// - public class TransactionMetaInfo - { - /// - /// The transaction information. - /// - public TransactionInfo Transaction { get; set; } - - /// - /// The metadata information. - /// - public TransactionMeta Meta { get; set; } - } - - /// - /// Represents the reward information related to a given account. - /// - public class RewardInfo - { - /// - /// The account pubkey as base58 encoded string. - /// - public string Pubkey { get; set; } - /// - /// Number of reward lamports credited or debited by the account. - /// - public long Lamports { get; set; } - - /// - /// Account balance in lamports after the reward was applied. - /// - public ulong PostBalance { get; set; } - - /// - /// Type of the reward. - /// - public RewardType RewardType { get; set; } - } - - /// - /// The type of the reward. - /// - public enum RewardType - { - /// - /// Default value in case the returned value is undefined. - /// - Unknown, - - /// - /// Fee reward. - /// - Fee, - - /// - /// Rent reward. - /// - Rent, - - /// - /// Voting reward. - /// - Voting, - - /// - /// Staking reward. - /// - Staking - } - - /// - /// Represents a transaction. - /// - public class TransactionInfo - { - /// - /// The signatures of this transaction. - /// - public string[] Signatures { get; set; } - - /// - /// The message contents of the transaction. - /// - public TransactionContentInfo Message { get; set; } - } - - /// - /// Represents the contents of the trasaction. - /// - public class TransactionContentInfo - { - /// - /// List of base-58 encoded public keys used by the transaction, including by the instructions and for signatures. - /// - public string[] AccountKeys { get; set; } - - /// - /// Details the account types and signatures required by the transaction. - /// - public TransactionHeaderInfo Header { get; set; } - - /// - /// A base-58 encoded hash of a recent block in the ledger used to prevent transaction duplication and to give transactions lifetimes. - /// - public string RecentBlockhash { get; set; } - - /// - /// List of program instructions that will be executed in sequence and committed in one atomic transaction if all succeed. - /// - public InstructionInfo[] Instructions { get; set; } - } - - /// - /// Details the number and type of accounts and signatures in a given transaction. - /// - public class TransactionHeaderInfo - { - /// - /// The total number of signatures required to make the transaction valid. - /// - public int NumRequiredSignatures { get; set; } - - /// - /// The last NumReadonlySignedAccounts of the signed keys are read-only accounts. - /// - public int NumReadonlySignedAccounts { get; set; } - - /// - /// The last NumReadonlyUnsignedAccounts of the unsigned keys are read-only accounts. - /// - public int NumReadonlyUnsignedAccounts { get; set; } - } - - /// - /// Represents the transaction metadata. - /// - public class TransactionMeta - { - /// - /// Possible transaction error. - /// - [JsonPropertyName("err")] - public TransactionError Error { get; set; } - - /// - /// Fee this transaction was charged. - /// - public ulong Fee { get; set; } - - /// - /// Collection of account balances from before the transaction was processed. - /// - public ulong[] PreBalances { get; set; } - - /// - /// Collection of account balances after the transaction was processed. - /// - public ulong[] PostBalances { get; set; } - - /// - /// List of inner instructions or omitted if inner instruction recording was not yet enabled during this transaction. - /// - public InnerInstruction[] InnerInstructions { get; set; } - - /// - /// List of token balances from before the transaction was processed or omitted if token balance recording was not yet enabled during this transaction. - /// - public TokenBalanceInfo[] PreTokenBalances { get; set; } - - /// - /// List of token balances from after the transaction was processed or omitted if token balance recording was not yet enabled during this transaction. - /// - public TokenBalanceInfo[] PostTokenBalances { get; set; } - - /// - /// Array of string log messages or omitted if log message recording was not yet enabled during this transaction. - /// - public string[] LogMessages { get; set; } - } - - /// - /// Represents the structure of a token balance metadata for a transaction. - /// - public class TokenBalanceInfo - { - /// - /// Index of the account in which the token balance is provided for. - /// - public int AccountIndex { get; set; } - - /// - /// Pubkey of the token's mint. - /// - public string Mint { get; set; } - - /// - /// Token balance details. - /// - public TokenBalance UiTokenAmount { get; set; } - } - - /// - /// Represents an inner instruction. Inner instruction are cross-program instructions that are invoked during transaction processing. - /// - public class InnerInstruction - { - /// - /// Index of the transaction instruction from which the inner instruction(s) originated - /// - public int Index { get; set; } - - /// - /// List of program instructions that will be executed in sequence and committed in one atomic transaction if all succeed. - /// - public InstructionInfo[] Instructions { get; set; } - } - - /// - /// Represents the data of given instruction. - /// - public class InstructionInfo - { - /// - /// Index into the Message.AccountKeys array indicating the program account that executes this instruction. - /// - public int ProgramIdIndex { get; set; } - - /// - /// List of ordered indices into the Message.AccountKeys array indicating which accounts to pass to the program. - /// - public int[] Accounts { get; set; } - - /// - /// The program input data encoded in a base-58 string. - /// - public string Data { get; set; } - } - - /// - /// Represents the block commitment info. - /// - public class BlockCommitment - { - /// - /// A list of values representing the amount of cluster stake in lamports that has - /// voted onn the block at each depth from 0 to (max lockout history + 1). - /// - public ulong[] Commitment { get; set; } - - /// - /// Total active stake, in lamports, of the current epoch. - /// - public ulong TotalStake { get; set; } - } - - /// - /// Represents the fee calculator info. - /// - public class FeeCalculator - { - /// - /// The amount, in lamports, to be paid per signature. - /// - public ulong LamportsPerSignature { get; set; } - } - - /// - /// Represents the fee calculator info. - /// - public class FeeCalculatorInfo - { - /// - /// The fee calculator info. - /// - public FeeCalculator FeeCalculator { get; set; } - } - - /// - /// Represents block hash info. - /// - public class BlockHash - { - /// - /// A base-58 encoded public key representing the block hash. - /// - public string Blockhash { get; set; } - - /// - /// The fee calculator data. - /// - public FeeCalculator FeeCalculator { get; set; } - } +// ReSharper disable UnusedAutoPropertyAccessor.Global +// ReSharper disable ClassNeverInstantiated.Global + +using System.Text.Json.Serialization; + +namespace Solnet.Rpc.Models +{ + /// + /// Represents the block info. + /// + public class BlockInfo + { + /// + /// Estimated block production time. + /// + public long BlockTime { get; set; } + + /// + /// A base-58 encoded public key representing the block hash. + /// + public string Blockhash { get; set; } + + /// + /// A base-58 encoded public key representing the block hash of this block's parent. + /// + /// If the parent block is no longer available due to ledger cleanup, this field will return + /// '11111111111111111111111111111111' + /// + /// + public string PreviousBlockhash { get; set; } + + /// + /// The slot index of this block's parent. + /// + public ulong ParentSlot { get; set; } + + /// + /// The number of blocks beneath this block. + /// + public long BlockHeight { get; set; } + + /// + /// The rewards for this given block. + /// + public RewardInfo[] Rewards { get; set; } + + /// + /// Collection of transactions and their metadata within this block. + /// + public TransactionMetaInfo[] Transactions { get; set; } + } + + /// + /// Represents the transaction, metadata and its containing slot. + /// + public class TransactionMetaSlotInfo : TransactionMetaInfo + { + /// + /// The slot this transaction was processed in. + /// + public ulong Slot { get; set; } + + /// + /// Estimated block production time. + /// + public long? BlockTime { get; set; } + } + + + /// + /// Represents the tuple transaction and metadata. + /// + public class TransactionMetaInfo + { + /// + /// The transaction information. + /// + public TransactionInfo Transaction { get; set; } + + /// + /// The metadata information. + /// + public TransactionMeta Meta { get; set; } + } + + /// + /// Represents the reward information related to a given account. + /// + public class RewardInfo + { + /// + /// The account pubkey as base58 encoded string. + /// + public string Pubkey { get; set; } + /// + /// Number of reward lamports credited or debited by the account. + /// + public long Lamports { get; set; } + + /// + /// Account balance in lamports after the reward was applied. + /// + public ulong PostBalance { get; set; } + + /// + /// Type of the reward. + /// + public RewardType RewardType { get; set; } + } + + /// + /// The type of the reward. + /// + public enum RewardType + { + /// + /// Default value in case the returned value is undefined. + /// + Unknown, + + /// + /// Fee reward. + /// + Fee, + + /// + /// Rent reward. + /// + Rent, + + /// + /// Voting reward. + /// + Voting, + + /// + /// Staking reward. + /// + Staking + } + + /// + /// Represents a transaction. + /// + public class TransactionInfo + { + /// + /// The signatures of this transaction. + /// + public string[] Signatures { get; set; } + + /// + /// The message contents of the transaction. + /// + public TransactionContentInfo Message { get; set; } + } + + /// + /// Represents the contents of the trasaction. + /// + public class TransactionContentInfo + { + /// + /// List of base-58 encoded public keys used by the transaction, including by the instructions and for signatures. + /// + public string[] AccountKeys { get; set; } + + /// + /// Details the account types and signatures required by the transaction. + /// + public TransactionHeaderInfo Header { get; set; } + + /// + /// A base-58 encoded hash of a recent block in the ledger used to prevent transaction duplication and to give transactions lifetimes. + /// + public string RecentBlockhash { get; set; } + + /// + /// List of program instructions that will be executed in sequence and committed in one atomic transaction if all succeed. + /// + public InstructionInfo[] Instructions { get; set; } + } + + /// + /// Details the number and type of accounts and signatures in a given transaction. + /// + public class TransactionHeaderInfo + { + /// + /// The total number of signatures required to make the transaction valid. + /// + public int NumRequiredSignatures { get; set; } + + /// + /// The last NumReadonlySignedAccounts of the signed keys are read-only accounts. + /// + public int NumReadonlySignedAccounts { get; set; } + + /// + /// The last NumReadonlyUnsignedAccounts of the unsigned keys are read-only accounts. + /// + public int NumReadonlyUnsignedAccounts { get; set; } + } + + /// + /// Represents the transaction metadata. + /// + public class TransactionMeta + { + /// + /// Possible transaction error. + /// + [JsonPropertyName("err")] + public TransactionError Error { get; set; } + + /// + /// Fee this transaction was charged. + /// + public ulong Fee { get; set; } + + /// + /// Collection of account balances from before the transaction was processed. + /// + public ulong[] PreBalances { get; set; } + + /// + /// Collection of account balances after the transaction was processed. + /// + public ulong[] PostBalances { get; set; } + + /// + /// List of inner instructions or omitted if inner instruction recording was not yet enabled during this transaction. + /// + public InnerInstruction[] InnerInstructions { get; set; } + + /// + /// List of token balances from before the transaction was processed or omitted if token balance recording was not yet enabled during this transaction. + /// + public TokenBalanceInfo[] PreTokenBalances { get; set; } + + /// + /// List of token balances from after the transaction was processed or omitted if token balance recording was not yet enabled during this transaction. + /// + public TokenBalanceInfo[] PostTokenBalances { get; set; } + + /// + /// Array of string log messages or omitted if log message recording was not yet enabled during this transaction. + /// + public string[] LogMessages { get; set; } + } + + /// + /// Represents the structure of a token balance metadata for a transaction. + /// + public class TokenBalanceInfo + { + /// + /// Index of the account in which the token balance is provided for. + /// + public int AccountIndex { get; set; } + + /// + /// Pubkey of the token's mint. + /// + public string Mint { get; set; } + + /// + /// Token balance details. + /// + public TokenBalance UiTokenAmount { get; set; } + } + + /// + /// Represents an inner instruction. Inner instruction are cross-program instructions that are invoked during transaction processing. + /// + public class InnerInstruction + { + /// + /// Index of the transaction instruction from which the inner instruction(s) originated + /// + public int Index { get; set; } + + /// + /// List of program instructions that will be executed in sequence and committed in one atomic transaction if all succeed. + /// + public InstructionInfo[] Instructions { get; set; } + } + + /// + /// Represents the data of given instruction. + /// + public class InstructionInfo + { + /// + /// Index into the Message.AccountKeys array indicating the program account that executes this instruction. + /// + public int ProgramIdIndex { get; set; } + + /// + /// List of ordered indices into the Message.AccountKeys array indicating which accounts to pass to the program. + /// + public int[] Accounts { get; set; } + + /// + /// The program input data encoded in a base-58 string. + /// + public string Data { get; set; } + } + + /// + /// Represents the block commitment info. + /// + public class BlockCommitment + { + /// + /// A list of values representing the amount of cluster stake in lamports that has + /// voted onn the block at each depth from 0 to (max lockout history + 1). + /// + public ulong[] Commitment { get; set; } + + /// + /// Total active stake, in lamports, of the current epoch. + /// + public ulong TotalStake { get; set; } + } + + /// + /// Represents the fee calculator info. + /// + public class FeeCalculator + { + /// + /// The amount, in lamports, to be paid per signature. + /// + public ulong LamportsPerSignature { get; set; } + } + + /// + /// Represents the fee calculator info. + /// + public class FeeCalculatorInfo + { + /// + /// The fee calculator info. + /// + public FeeCalculator FeeCalculator { get; set; } + } + + /// + /// Represents block hash info. + /// + public class BlockHash + { + /// + /// A base-58 encoded public key representing the block hash. + /// + public string Blockhash { get; set; } + + /// + /// The fee calculator data. + /// + public FeeCalculator FeeCalculator { get; set; } + } } \ No newline at end of file diff --git a/src/Solnet.Rpc/Models/BlockProductionInfo.cs b/src/Solnet.Rpc/Models/BlockProductionInfo.cs index 3dc21e67..4281ee16 100644 --- a/src/Solnet.Rpc/Models/BlockProductionInfo.cs +++ b/src/Solnet.Rpc/Models/BlockProductionInfo.cs @@ -1,38 +1,38 @@ -using System.Collections.Generic; - -namespace Solnet.Rpc.Models -{ - /// - /// Holds the block production information. - /// - public class BlockProductionInfo - { - /// - /// The block production as a map from the validator to a list - /// of the number of leader slots and number of blocks produced - /// - public Dictionary> ByIdentity { get; set; } - - /// - /// The block production range by slots. - /// - public SlotRange Range { get; set; } - } - - /// - /// Represents a slot range. - /// - public class SlotRange - { - /// - /// The first slot of the range (inclusive). - /// - public ulong FirstSlot { get; set; } - - /// - /// The last slot of the range (inclusive). - /// - public ulong LastSlot { get; set; } - - } +using System.Collections.Generic; + +namespace Solnet.Rpc.Models +{ + /// + /// Holds the block production information. + /// + public class BlockProductionInfo + { + /// + /// The block production as a map from the validator to a list + /// of the number of leader slots and number of blocks produced + /// + public Dictionary> ByIdentity { get; set; } + + /// + /// The block production range by slots. + /// + public SlotRange Range { get; set; } + } + + /// + /// Represents a slot range. + /// + public class SlotRange + { + /// + /// The first slot of the range (inclusive). + /// + public ulong FirstSlot { get; set; } + + /// + /// The last slot of the range (inclusive). + /// + public ulong LastSlot { get; set; } + + } } \ No newline at end of file diff --git a/src/Solnet.Rpc/Models/ClusterNode.cs b/src/Solnet.Rpc/Models/ClusterNode.cs index 9dd944a0..76f462fe 100644 --- a/src/Solnet.Rpc/Models/ClusterNode.cs +++ b/src/Solnet.Rpc/Models/ClusterNode.cs @@ -1,43 +1,43 @@ -// ReSharper disable ClassNeverInstantiated.Global -using System.Text.Json.Serialization; - -namespace Solnet.Rpc.Models -{ - /// - /// Represents a node in the cluster. - /// - public class ClusterNode - { - /// - /// Gossip network address for the node. - /// - public string Gossip { get; set; } - - /// - /// A base-58 encoded public key associated with the node. - /// - [JsonPropertyName("pubkey")] - public string PublicKey { get; set; } - - /// - /// JSON RPC network address for the node. The service may not be enabled. - /// - public string Rpc { get; set; } - - /// - /// TPU network address for the node. - /// - public string Tpu { get; set; } - - /// - /// The software version of the node. The information may not be available. - /// - public string Version { get; set; } - - /// - /// Unique identifier of the current software's feature set. - /// - public ulong? FeatureSet { get; set; } - public ulong ShredVersion { get; set; } - } +// ReSharper disable ClassNeverInstantiated.Global +using System.Text.Json.Serialization; + +namespace Solnet.Rpc.Models +{ + /// + /// Represents a node in the cluster. + /// + public class ClusterNode + { + /// + /// Gossip network address for the node. + /// + public string Gossip { get; set; } + + /// + /// A base-58 encoded public key associated with the node. + /// + [JsonPropertyName("pubkey")] + public string PublicKey { get; set; } + + /// + /// JSON RPC network address for the node. The service may not be enabled. + /// + public string Rpc { get; set; } + + /// + /// TPU network address for the node. + /// + public string Tpu { get; set; } + + /// + /// The software version of the node. The information may not be available. + /// + public string Version { get; set; } + + /// + /// Unique identifier of the current software's feature set. + /// + public ulong? FeatureSet { get; set; } + public ulong ShredVersion { get; set; } + } } \ No newline at end of file diff --git a/src/Solnet.Rpc/Models/Epoch.cs b/src/Solnet.Rpc/Models/Epoch.cs index ac72d41c..1ef0fc1b 100644 --- a/src/Solnet.Rpc/Models/Epoch.cs +++ b/src/Solnet.Rpc/Models/Epoch.cs @@ -1,64 +1,64 @@ -namespace Solnet.Rpc.Models -{ - /// - /// Represents information about the current epoch. - /// - public class EpochInfo - { - /// - /// The current slot. - /// - public ulong AbsoluteSlot { get; set; } - - /// - /// The current block height. - /// - public ulong BlockHeight { get; set; } - - /// - /// The current epoch. - /// - public ulong Epoch { get; set; } - - /// - /// The current slot relative to the start of the current epoch. - /// - public ulong SlotIndex { get; set; } - - /// - /// The number of slots in this epoch - /// - public ulong SlotsInEpoch { get; set; } - } - - /// - /// Represents information about the epoch schedule. - /// - public class EpochScheduleInfo - { - /// - /// The maximum number of slots in each epoch. - /// - public ulong SlotsPerEpoch { get; set; } - - /// - /// The number of slots before beginning of an epoch to calculate a leader schedule for that epoch. - /// - public ulong LeaderScheduleSlotOffset { get; set; } - - /// - /// The first normal-length epoch. - /// - public ulong FirstNormalEpoch { get; set; } - - /// - /// The first normal-length slot. - /// - public ulong FirstNormalSlot { get; set; } - - /// - /// Whether epochs start short and grow. - /// - public bool Warmup { get; set; } - } +namespace Solnet.Rpc.Models +{ + /// + /// Represents information about the current epoch. + /// + public class EpochInfo + { + /// + /// The current slot. + /// + public ulong AbsoluteSlot { get; set; } + + /// + /// The current block height. + /// + public ulong BlockHeight { get; set; } + + /// + /// The current epoch. + /// + public ulong Epoch { get; set; } + + /// + /// The current slot relative to the start of the current epoch. + /// + public ulong SlotIndex { get; set; } + + /// + /// The number of slots in this epoch + /// + public ulong SlotsInEpoch { get; set; } + } + + /// + /// Represents information about the epoch schedule. + /// + public class EpochScheduleInfo + { + /// + /// The maximum number of slots in each epoch. + /// + public ulong SlotsPerEpoch { get; set; } + + /// + /// The number of slots before beginning of an epoch to calculate a leader schedule for that epoch. + /// + public ulong LeaderScheduleSlotOffset { get; set; } + + /// + /// The first normal-length epoch. + /// + public ulong FirstNormalEpoch { get; set; } + + /// + /// The first normal-length slot. + /// + public ulong FirstNormalSlot { get; set; } + + /// + /// Whether epochs start short and grow. + /// + public bool Warmup { get; set; } + } } \ No newline at end of file diff --git a/src/Solnet.Rpc/Models/ErrorResult.cs b/src/Solnet.Rpc/Models/ErrorResult.cs index 603e46d0..d64d479a 100644 --- a/src/Solnet.Rpc/Models/ErrorResult.cs +++ b/src/Solnet.Rpc/Models/ErrorResult.cs @@ -1,10 +1,10 @@ -using System.Text.Json.Serialization; - -namespace Solnet.Rpc.Models -{ - public class ErrorResult - { - [JsonPropertyName("err")] - public string Error { get; set; } - } +using System.Text.Json.Serialization; + +namespace Solnet.Rpc.Models +{ + public class ErrorResult + { + [JsonPropertyName("err")] + public string Error { get; set; } + } } \ No newline at end of file diff --git a/src/Solnet.Rpc/Models/Fees.cs b/src/Solnet.Rpc/Models/Fees.cs index e784b7f2..dcf30ddd 100644 --- a/src/Solnet.Rpc/Models/Fees.cs +++ b/src/Solnet.Rpc/Models/Fees.cs @@ -1,70 +1,70 @@ -namespace Solnet.Rpc.Models -{ - /// - /// Represents the fee rate governor. - /// - public class FeeRateGovernor - { - /// - /// Percentage of fees collected to be destroyed. - /// - public decimal BurnPercent { get; set; } - - /// - /// Highest value LamportsPerSignature can attain for the next slot. - /// - public ulong MaxLamportsPerSignature { get; set; } - - /// - /// Smallest value LamportsPerSignature can attain for the next slot. - /// - public ulong MinLamportsPerSignature { get; set; } - - /// - /// Desired fee rate for the cluster. - /// - public ulong TargetLamportsPerSignature { get; set; } - - /// - /// Desired signature rate for the cluster. - /// - public ulong TargetSignaturesPerSlot { get; set; } - } - - /// - /// Represents the fee rate governor info. - /// - public class FeeRateGovernorInfo - { - /// - /// The fee rate governor. - /// - public FeeRateGovernor FeeRateGovernor { get; set; } - } - - /// - /// Represents information about the fees. - /// - public class FeesInfo - { - /// - /// A block hash as base-58 encoded string. - /// - public string Blockhash { get; set; } - - /// - /// The fee calculator for this block hash. - /// - public FeeCalculator FeeCalculator { get; set; } - - /// - /// DEPRECATED - this value is inaccurate and should not be relied upon - /// - public ulong LastValidSlot { get; set; } - - /// - /// Last block height at which a blockhash will be valid. - /// - public ulong LastValidBlockHeight { get; set; } - } +namespace Solnet.Rpc.Models +{ + /// + /// Represents the fee rate governor. + /// + public class FeeRateGovernor + { + /// + /// Percentage of fees collected to be destroyed. + /// + public decimal BurnPercent { get; set; } + + /// + /// Highest value LamportsPerSignature can attain for the next slot. + /// + public ulong MaxLamportsPerSignature { get; set; } + + /// + /// Smallest value LamportsPerSignature can attain for the next slot. + /// + public ulong MinLamportsPerSignature { get; set; } + + /// + /// Desired fee rate for the cluster. + /// + public ulong TargetLamportsPerSignature { get; set; } + + /// + /// Desired signature rate for the cluster. + /// + public ulong TargetSignaturesPerSlot { get; set; } + } + + /// + /// Represents the fee rate governor info. + /// + public class FeeRateGovernorInfo + { + /// + /// The fee rate governor. + /// + public FeeRateGovernor FeeRateGovernor { get; set; } + } + + /// + /// Represents information about the fees. + /// + public class FeesInfo + { + /// + /// A block hash as base-58 encoded string. + /// + public string Blockhash { get; set; } + + /// + /// The fee calculator for this block hash. + /// + public FeeCalculator FeeCalculator { get; set; } + + /// + /// DEPRECATED - this value is inaccurate and should not be relied upon + /// + public ulong LastValidSlot { get; set; } + + /// + /// Last block height at which a blockhash will be valid. + /// + public ulong LastValidBlockHeight { get; set; } + } } \ No newline at end of file diff --git a/src/Solnet.Rpc/Models/Identity.cs b/src/Solnet.Rpc/Models/Identity.cs index 61901eb7..0afd85f6 100644 --- a/src/Solnet.Rpc/Models/Identity.cs +++ b/src/Solnet.Rpc/Models/Identity.cs @@ -1,13 +1,13 @@ -namespace Solnet.Rpc.Models -{ - /// - /// Represents the identity public key for the current node. - /// - public class NodeIdentity - { - /// - /// The identity public key of the current node, as base-58 encoded string. - /// - public string Identity { get; set; } - } +namespace Solnet.Rpc.Models +{ + /// + /// Represents the identity public key for the current node. + /// + public class NodeIdentity + { + /// + /// The identity public key of the current node, as base-58 encoded string. + /// + public string Identity { get; set; } + } } \ No newline at end of file diff --git a/src/Solnet.Rpc/Models/Inflation.cs b/src/Solnet.Rpc/Models/Inflation.cs index 42529489..39c344c6 100644 --- a/src/Solnet.Rpc/Models/Inflation.cs +++ b/src/Solnet.Rpc/Models/Inflation.cs @@ -1,86 +1,86 @@ -namespace Solnet.Rpc.Models -{ - /// - /// Represents inflation governor information. - /// - public class InflationGovernor - { - /// - /// The initial inflation percentage from time zero. - /// - public decimal Initial { get; set; } - - /// - /// The terminal inflation percentage. - /// - public decimal Terminal { get; set; } - - /// - /// The rate per year at which inflation is lowered. - /// Rate reduction is derived using the target slot time as per genesis config. - /// - public decimal Taper { get; set; } - - /// - /// Percentage of total inflation allocated to the foundation. - /// - public decimal Foundation { get; set; } - - /// - /// Duration of foundation pool inflation in years. - /// - public decimal FoundationTerm { get; set; } - } - - /// - /// Represents the inflation rate information. - /// - public class InflationRate - { - /// - /// Epoch for which these values are valid. - /// - public decimal Epoch { get; set; } - - /// - /// Percentage of total inflation allocated to the foundation. - /// - public decimal Foundation { get; set; } - - /// - /// Percentage of total inflation. - /// - public decimal Total { get; set; } - - /// - /// Percentage of total inflation allocated to validators. - /// - public decimal Validator { get; set; } - } - - /// - /// Represents the inflation reward for a certain address. - /// - public class InflationReward - { - /// - /// Epoch for which a reward occurred. - /// - public ulong Epoch { get; set; } - - /// - /// The slot in which the rewards are effective. - /// - public ulong EffectiveSlot { get; set; } - - /// - /// The reward amount in lamports. - /// - public ulong Amount { get; set; } - - /// - /// Post balance of the account in lamports. - /// - public ulong PostBalance { get; set; } - } +namespace Solnet.Rpc.Models +{ + /// + /// Represents inflation governor information. + /// + public class InflationGovernor + { + /// + /// The initial inflation percentage from time zero. + /// + public decimal Initial { get; set; } + + /// + /// The terminal inflation percentage. + /// + public decimal Terminal { get; set; } + + /// + /// The rate per year at which inflation is lowered. + /// Rate reduction is derived using the target slot time as per genesis config. + /// + public decimal Taper { get; set; } + + /// + /// Percentage of total inflation allocated to the foundation. + /// + public decimal Foundation { get; set; } + + /// + /// Duration of foundation pool inflation in years. + /// + public decimal FoundationTerm { get; set; } + } + + /// + /// Represents the inflation rate information. + /// + public class InflationRate + { + /// + /// Epoch for which these values are valid. + /// + public decimal Epoch { get; set; } + + /// + /// Percentage of total inflation allocated to the foundation. + /// + public decimal Foundation { get; set; } + + /// + /// Percentage of total inflation. + /// + public decimal Total { get; set; } + + /// + /// Percentage of total inflation allocated to validators. + /// + public decimal Validator { get; set; } + } + + /// + /// Represents the inflation reward for a certain address. + /// + public class InflationReward + { + /// + /// Epoch for which a reward occurred. + /// + public ulong Epoch { get; set; } + + /// + /// The slot in which the rewards are effective. + /// + public ulong EffectiveSlot { get; set; } + + /// + /// The reward amount in lamports. + /// + public ulong Amount { get; set; } + + /// + /// Post balance of the account in lamports. + /// + public ulong PostBalance { get; set; } + } } \ No newline at end of file diff --git a/src/Solnet.Rpc/Models/InstructionError.cs b/src/Solnet.Rpc/Models/InstructionError.cs index 35bd63b4..2e8d2946 100644 --- a/src/Solnet.Rpc/Models/InstructionError.cs +++ b/src/Solnet.Rpc/Models/InstructionError.cs @@ -1,67 +1,67 @@ -namespace Solnet.Rpc.Models -{ - public class InstructionError - { - public int InstructionIndex { get; set; } - - public InstructionErrorType Type { get; set; } - - public int? CustomError { get; set; } - - public string BorshIoError { get; set; } - } - - - public enum InstructionErrorType - { - GenericError, - InvalidArgument, - InvalidInstructionData, - InvalidAccountData, - AccountDataTooSmall, - InsufficientFunds, - IncorrectProgramId, - MissingRequiredSignature, - AccountAlreadyInitialized, - UninitializedAccount, - UnbalancedInstruction, - ModifiedProgramId, - ExternalAccountLamportSpend, - ExternalAccountDataModified, - ReadonlyLamportChange, - ReadonlyDataModified, - DuplicateAccountIndex, - ExecutableModified, - RentEpochModified, - NotEnoughAccountKeys, - AccountDataSizeChanged, - AccountNotExecutable, - AccountBorrowFailed, - AccountBorrowOutstanding, - DuplicateAccountOutOfSync, - Custom, //(u32) - InvalidError, - ExecutableDataModified, - ExecutableLamportChange, - ExecutableAccountNotRentExempt, - UnsupportedProgramId, - CallDepth, - MissingAccount, - ReentrancyNotAllowed, - MaxSeedLengthExceeded, - InvalidSeeds, - InvalidRealloc, - ComputationalBudgetExceeded, - PrivilegeEscalation, - ProgramEnvironmentSetupFailure, - ProgramFailedToComplete, - ProgramFailedToCompile, - Immutable, - IncorrectAuthority, - BorshIoError, //(String) - AccountNotRentExempt, - InvalidAccountOwner, - ArithmeticOverflow, - UnsupportedSysvar - } +namespace Solnet.Rpc.Models +{ + public class InstructionError + { + public int InstructionIndex { get; set; } + + public InstructionErrorType Type { get; set; } + + public int? CustomError { get; set; } + + public string BorshIoError { get; set; } + } + + + public enum InstructionErrorType + { + GenericError, + InvalidArgument, + InvalidInstructionData, + InvalidAccountData, + AccountDataTooSmall, + InsufficientFunds, + IncorrectProgramId, + MissingRequiredSignature, + AccountAlreadyInitialized, + UninitializedAccount, + UnbalancedInstruction, + ModifiedProgramId, + ExternalAccountLamportSpend, + ExternalAccountDataModified, + ReadonlyLamportChange, + ReadonlyDataModified, + DuplicateAccountIndex, + ExecutableModified, + RentEpochModified, + NotEnoughAccountKeys, + AccountDataSizeChanged, + AccountNotExecutable, + AccountBorrowFailed, + AccountBorrowOutstanding, + DuplicateAccountOutOfSync, + Custom, //(u32) + InvalidError, + ExecutableDataModified, + ExecutableLamportChange, + ExecutableAccountNotRentExempt, + UnsupportedProgramId, + CallDepth, + MissingAccount, + ReentrancyNotAllowed, + MaxSeedLengthExceeded, + InvalidSeeds, + InvalidRealloc, + ComputationalBudgetExceeded, + PrivilegeEscalation, + ProgramEnvironmentSetupFailure, + ProgramFailedToComplete, + ProgramFailedToCompile, + Immutable, + IncorrectAuthority, + BorshIoError, //(String) + AccountNotRentExempt, + InvalidAccountOwner, + ArithmeticOverflow, + UnsupportedSysvar + } } \ No newline at end of file diff --git a/src/Solnet.Rpc/Models/Logs.cs b/src/Solnet.Rpc/Models/Logs.cs index 374ead1f..fa249b12 100644 --- a/src/Solnet.Rpc/Models/Logs.cs +++ b/src/Solnet.Rpc/Models/Logs.cs @@ -1,58 +1,58 @@ -// ReSharper disable UnusedAutoPropertyAccessor.Global -// ReSharper disable ClassNeverInstantiated.Global - -using System.Text.Json.Serialization; - -namespace Solnet.Rpc.Models -{ - /// - /// Represents a log during transaction simulation. - /// - public class Log - { - /// - /// The error associated with the transaction simulation. - /// - [JsonPropertyName("err")] - public string Error { get; set; } - - /// - /// The log messages the transaction instructions output during execution. - /// - /// This will be null if the simulation failed before the transaction was able to execute. - /// - /// - public string[] Logs { get; set; } - } - - /// - /// Represents a log message when subscribing to the log output of the Streaming RPC. - /// - public class LogInfo : Log - { - /// - /// The signature of the transaction. - /// - public string Signature { get; set; } - } - - public class SimulationLogs - { - public object[] Accounts { get; set; } - - /// - /// The error associated with the transaction simulation. - /// - [JsonPropertyName("err")] - public TransactionError Error { get; set; } - - - /// - /// The log messages the transaction instructions output during execution. - /// - /// This will be null if the simulation failed before the transaction was able to execute. - /// - /// - public string[] Logs { get; set; } - } +// ReSharper disable UnusedAutoPropertyAccessor.Global +// ReSharper disable ClassNeverInstantiated.Global + +using System.Text.Json.Serialization; + +namespace Solnet.Rpc.Models +{ + /// + /// Represents a log during transaction simulation. + /// + public class Log + { + /// + /// The error associated with the transaction simulation. + /// + [JsonPropertyName("err")] + public string Error { get; set; } + + /// + /// The log messages the transaction instructions output during execution. + /// + /// This will be null if the simulation failed before the transaction was able to execute. + /// + /// + public string[] Logs { get; set; } + } + + /// + /// Represents a log message when subscribing to the log output of the Streaming RPC. + /// + public class LogInfo : Log + { + /// + /// The signature of the transaction. + /// + public string Signature { get; set; } + } + + public class SimulationLogs + { + public object[] Accounts { get; set; } + + /// + /// The error associated with the transaction simulation. + /// + [JsonPropertyName("err")] + public TransactionError Error { get; set; } + + + /// + /// The log messages the transaction instructions output during execution. + /// + /// This will be null if the simulation failed before the transaction was able to execute. + /// + /// + public string[] Logs { get; set; } + } } \ No newline at end of file diff --git a/src/Solnet.Rpc/Models/Performance.cs b/src/Solnet.Rpc/Models/Performance.cs index 0735203e..8d067dbf 100644 --- a/src/Solnet.Rpc/Models/Performance.cs +++ b/src/Solnet.Rpc/Models/Performance.cs @@ -1,28 +1,28 @@ -namespace Solnet.Rpc.Models -{ - /// - /// Represents a performance sample. - /// - public class PerformanceSample - { - /// - /// Slot in which sample was taken at. - /// - public ulong Slot { get; set; } - - /// - /// Number of transactions in sample. - /// - public ulong NumTransactions { get; set; } - - /// - /// Number of slots in sample - /// - public ulong NumSlots { get; set; } - - /// - /// Number of seconds in a sample window. - /// - public int SamplePeriodSecs { get; set; } - } +namespace Solnet.Rpc.Models +{ + /// + /// Represents a performance sample. + /// + public class PerformanceSample + { + /// + /// Slot in which sample was taken at. + /// + public ulong Slot { get; set; } + + /// + /// Number of transactions in sample. + /// + public ulong NumTransactions { get; set; } + + /// + /// Number of slots in sample + /// + public ulong NumSlots { get; set; } + + /// + /// Number of seconds in a sample window. + /// + public int SamplePeriodSecs { get; set; } + } } \ No newline at end of file diff --git a/src/Solnet.Rpc/Models/Signature.cs b/src/Solnet.Rpc/Models/Signature.cs index af5aa197..819826aa 100644 --- a/src/Solnet.Rpc/Models/Signature.cs +++ b/src/Solnet.Rpc/Models/Signature.cs @@ -1,46 +1,46 @@ -using System.Text.Json.Serialization; - -namespace Solnet.Rpc.Models -{ - /// - /// Represents the signature status information. - /// - public class SignatureStatusInfo - { - /// - /// The slot the transaction was processed in. - /// - public ulong Slot { get; set; } - - /// - /// The number of blocks since signature confirmation. - /// - public ulong? Confirmations { get; set; } - - /// - /// The error if the transaction failed, null if it succeeded. - /// - [JsonPropertyName("err")] - public TransactionError Error { get; set; } - - /// - /// The transaction's cluster confirmation status, either "processed", "confirmed" or "finalized". - /// - public string ConfirmationStatus { get; set; } - - /// - /// Memo associated with the transaction, null if no memo is present. - /// - public string Memo { get; set; } - - /// - /// The transaction signature as base-58 encoded string. - /// - public string Signature { get; set; } - - /// - /// Estimated production time as Unix timestamp, null if not available. - /// - public ulong? BlockTime { get; set; } - } +using System.Text.Json.Serialization; + +namespace Solnet.Rpc.Models +{ + /// + /// Represents the signature status information. + /// + public class SignatureStatusInfo + { + /// + /// The slot the transaction was processed in. + /// + public ulong Slot { get; set; } + + /// + /// The number of blocks since signature confirmation. + /// + public ulong? Confirmations { get; set; } + + /// + /// The error if the transaction failed, null if it succeeded. + /// + [JsonPropertyName("err")] + public TransactionError Error { get; set; } + + /// + /// The transaction's cluster confirmation status, either "processed", "confirmed" or "finalized". + /// + public string ConfirmationStatus { get; set; } + + /// + /// Memo associated with the transaction, null if no memo is present. + /// + public string Memo { get; set; } + + /// + /// The transaction signature as base-58 encoded string. + /// + public string Signature { get; set; } + + /// + /// Estimated production time as Unix timestamp, null if not available. + /// + public ulong? BlockTime { get; set; } + } } \ No newline at end of file diff --git a/src/Solnet.Rpc/Models/SlotInfo.cs b/src/Solnet.Rpc/Models/SlotInfo.cs index a5e0ccfd..c748946d 100644 --- a/src/Solnet.Rpc/Models/SlotInfo.cs +++ b/src/Solnet.Rpc/Models/SlotInfo.cs @@ -1,25 +1,25 @@ -// ReSharper disable ClassNeverInstantiated.Global - -namespace Solnet.Rpc.Models -{ - /// - /// Represents the slot info. - /// - public class SlotInfo - { - /// - /// The parent slot. - /// - public int Parent { get; set; } - - /// - /// The root as set by the validator. - /// - public int Root { get; set; } - - /// - /// The current slot. - /// - public int Slot { get; set; } - } +// ReSharper disable ClassNeverInstantiated.Global + +namespace Solnet.Rpc.Models +{ + /// + /// Represents the slot info. + /// + public class SlotInfo + { + /// + /// The parent slot. + /// + public int Parent { get; set; } + + /// + /// The root as set by the validator. + /// + public int Root { get; set; } + + /// + /// The current slot. + /// + public int Slot { get; set; } + } } \ No newline at end of file diff --git a/src/Solnet.Rpc/Models/StakeActivation.cs b/src/Solnet.Rpc/Models/StakeActivation.cs index ca1456d6..1c69652d 100644 --- a/src/Solnet.Rpc/Models/StakeActivation.cs +++ b/src/Solnet.Rpc/Models/StakeActivation.cs @@ -1,23 +1,23 @@ -namespace Solnet.Rpc.Models -{ - /// - /// Represents the stake activation info. - /// - public class StakeActivationInfo - { - /// - /// Stake active during the epoch. - /// - public ulong Active { get; set; } - - /// - /// Stake inactive during the epoch. - /// - public ulong Inactive { get; set; } - - /// - /// The stake account's activation state, one of "active", "inactive", "activating", "deactivating". - /// - public string State { get; set; } - } +namespace Solnet.Rpc.Models +{ + /// + /// Represents the stake activation info. + /// + public class StakeActivationInfo + { + /// + /// Stake active during the epoch. + /// + public ulong Active { get; set; } + + /// + /// Stake inactive during the epoch. + /// + public ulong Inactive { get; set; } + + /// + /// The stake account's activation state, one of "active", "inactive", "activating", "deactivating". + /// + public string State { get; set; } + } } \ No newline at end of file diff --git a/src/Solnet.Rpc/Models/Supply.cs b/src/Solnet.Rpc/Models/Supply.cs index 59919dd1..59a32a2c 100644 --- a/src/Solnet.Rpc/Models/Supply.cs +++ b/src/Solnet.Rpc/Models/Supply.cs @@ -1,31 +1,31 @@ -// ReSharper disable ClassNeverInstantiated.Global -using System.Collections.Generic; - -namespace Solnet.Rpc.Models -{ - /// - /// Represents supply info. - /// - public class Supply - { - /// - /// Circulating supply in lamports. - /// - public ulong Circulating { get; set; } - - /// - /// Non-circulating supply in lamports. - /// - public ulong NonCirculating { get; set; } - - /// - /// A list of account addresses of non-circulating accounts, as strings. - /// - public IList NonCirculatingAccounts { get; set; } - - /// - /// Total supply in lamports. - /// - public ulong Total { get; set; } - } +// ReSharper disable ClassNeverInstantiated.Global +using System.Collections.Generic; + +namespace Solnet.Rpc.Models +{ + /// + /// Represents supply info. + /// + public class Supply + { + /// + /// Circulating supply in lamports. + /// + public ulong Circulating { get; set; } + + /// + /// Non-circulating supply in lamports. + /// + public ulong NonCirculating { get; set; } + + /// + /// A list of account addresses of non-circulating accounts, as strings. + /// + public IList NonCirculatingAccounts { get; set; } + + /// + /// Total supply in lamports. + /// + public ulong Total { get; set; } + } } \ No newline at end of file diff --git a/src/Solnet.Rpc/Models/TokenAccount.cs b/src/Solnet.Rpc/Models/TokenAccount.cs index 1e26f496..f2793606 100644 --- a/src/Solnet.Rpc/Models/TokenAccount.cs +++ b/src/Solnet.Rpc/Models/TokenAccount.cs @@ -1,23 +1,23 @@ -// ReSharper disable ClassNeverInstantiated.Global -// ReSharper disable UnusedAutoPropertyAccessor.Global -using System.Text.Json.Serialization; - -namespace Solnet.Rpc.Models -{ - /// - /// Represents a token account. - /// - public class TokenAccount - { - /// - /// The token account info. - /// - public TokenAccountInfo Account { get; set; } - - /// - /// A base-58 encoded public key representing the account's public key. - /// - [JsonPropertyName("pubkey")] - public string PublicKey { get; set; } - } +// ReSharper disable ClassNeverInstantiated.Global +// ReSharper disable UnusedAutoPropertyAccessor.Global +using System.Text.Json.Serialization; + +namespace Solnet.Rpc.Models +{ + /// + /// Represents a token account. + /// + public class TokenAccount + { + /// + /// The token account info. + /// + public TokenAccountInfo Account { get; set; } + + /// + /// A base-58 encoded public key representing the account's public key. + /// + [JsonPropertyName("pubkey")] + public string PublicKey { get; set; } + } } \ No newline at end of file diff --git a/src/Solnet.Rpc/Models/TransactionError.cs b/src/Solnet.Rpc/Models/TransactionError.cs index 538be8d7..3886837d 100644 --- a/src/Solnet.Rpc/Models/TransactionError.cs +++ b/src/Solnet.Rpc/Models/TransactionError.cs @@ -1,34 +1,34 @@ -using System.Text.Json.Serialization; - -namespace Solnet.Rpc.Models -{ - - [JsonConverter(typeof(TransactionErrorJsonConverter))] - public class TransactionError - { - public TransactionErrorType Type { get; set; } - - public InstructionError InstructionError { get; set; } - } - - public enum TransactionErrorType - { - AccountInUse, - AccountLoadedTwice, - AccountNotFound, - ProgramAccountNotFound, - InsufficientFundsForFee, - InvalidAccountForFee, - AlreadyProcessed, - BlockhashNotFound, - InstructionError, // (u8, InstructionError) - CallChainTooDeep, - MissingSignatureForFee, - InvalidAccountIndex, - SignatureFailure, - InvalidProgramForExecution, - SanitizeFailure, - ClusterMaintenance, - AccountBorrowOutstanding - } +using System.Text.Json.Serialization; + +namespace Solnet.Rpc.Models +{ + + [JsonConverter(typeof(TransactionErrorJsonConverter))] + public class TransactionError + { + public TransactionErrorType Type { get; set; } + + public InstructionError InstructionError { get; set; } + } + + public enum TransactionErrorType + { + AccountInUse, + AccountLoadedTwice, + AccountNotFound, + ProgramAccountNotFound, + InsufficientFundsForFee, + InvalidAccountForFee, + AlreadyProcessed, + BlockhashNotFound, + InstructionError, // (u8, InstructionError) + CallChainTooDeep, + MissingSignatureForFee, + InvalidAccountIndex, + SignatureFailure, + InvalidProgramForExecution, + SanitizeFailure, + ClusterMaintenance, + AccountBorrowOutstanding + } } \ No newline at end of file diff --git a/src/Solnet.Rpc/Models/TransactionErrorJsonConverter.cs b/src/Solnet.Rpc/Models/TransactionErrorJsonConverter.cs index 77479dc0..c670be12 100644 --- a/src/Solnet.Rpc/Models/TransactionErrorJsonConverter.cs +++ b/src/Solnet.Rpc/Models/TransactionErrorJsonConverter.cs @@ -1,123 +1,123 @@ -using System; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace Solnet.Rpc.Models -{ - public class TransactionErrorJsonConverter : JsonConverter - { - public override TransactionError Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - if (reader.TokenType == JsonTokenType.Null) return null; - - var err = new TransactionError(); - - if (reader.TokenType == JsonTokenType.String) - { - var enumValue = reader.GetString(); - - Enum.TryParse(enumValue, ignoreCase: false, out TransactionErrorType errorType); - err.Type = errorType; - reader.Read(); - return err; - } - - if (reader.TokenType != JsonTokenType.StartObject) - { - throw new JsonException("Unexpected error value."); - } - - reader.Read(); - - if (reader.TokenType != JsonTokenType.PropertyName) - { - throw new JsonException("Unexpected error value."); - } - - - { - var enumValue = reader.GetString(); - Enum.TryParse(enumValue, ignoreCase: false, out TransactionErrorType errorType); - err.Type = errorType; - } - - reader.Read(); - err.InstructionError = new InstructionError(); - - if (reader.TokenType != JsonTokenType.StartArray) - { - throw new JsonException("Unexpected error value."); - } - - reader.Read(); - - if (reader.TokenType != JsonTokenType.Number) - { - throw new JsonException("Unexpected error value."); - } - - err.InstructionError.InstructionIndex = reader.GetInt32(); - - reader.Read(); - - if (reader.TokenType == JsonTokenType.String) - { - var enumValue = reader.GetString(); - - Enum.TryParse(enumValue, ignoreCase: false, out InstructionErrorType errorType); - err.InstructionError.Type = errorType; - reader.Read(); //string - - reader.Read(); //endarray - return err; - } - - if (reader.TokenType != JsonTokenType.StartObject) - { - throw new JsonException("Unexpected error value."); - } - - reader.Read(); - - - if (reader.TokenType != JsonTokenType.PropertyName) - { - throw new JsonException("Unexpected error value."); - } - { - var enumValue = reader.GetString(); - Enum.TryParse(enumValue, ignoreCase: false, out InstructionErrorType errorType); - err.InstructionError.Type = errorType; - } - - reader.Read(); - - if (reader.TokenType == JsonTokenType.Number) - { - err.InstructionError.CustomError = reader.GetInt32(); - reader.Read(); //number - reader.Read(); //endobj - reader.Read(); //endarray - - return err; - } - - if (reader.TokenType != JsonTokenType.String) - { - throw new JsonException("Unexpected error value."); - } - - err.InstructionError.BorshIoError = reader.GetString(); - reader.Read(); //string - reader.Read(); //endobj - reader.Read(); //endarray - - return err; - } - - public override void Write(Utf8JsonWriter writer, TransactionError value, JsonSerializerOptions options) - { - throw new NotImplementedException(); - } - } +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Solnet.Rpc.Models +{ + public class TransactionErrorJsonConverter : JsonConverter + { + public override TransactionError Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.Null) return null; + + var err = new TransactionError(); + + if (reader.TokenType == JsonTokenType.String) + { + var enumValue = reader.GetString(); + + Enum.TryParse(enumValue, ignoreCase: false, out TransactionErrorType errorType); + err.Type = errorType; + reader.Read(); + return err; + } + + if (reader.TokenType != JsonTokenType.StartObject) + { + throw new JsonException("Unexpected error value."); + } + + reader.Read(); + + if (reader.TokenType != JsonTokenType.PropertyName) + { + throw new JsonException("Unexpected error value."); + } + + + { + var enumValue = reader.GetString(); + Enum.TryParse(enumValue, ignoreCase: false, out TransactionErrorType errorType); + err.Type = errorType; + } + + reader.Read(); + err.InstructionError = new InstructionError(); + + if (reader.TokenType != JsonTokenType.StartArray) + { + throw new JsonException("Unexpected error value."); + } + + reader.Read(); + + if (reader.TokenType != JsonTokenType.Number) + { + throw new JsonException("Unexpected error value."); + } + + err.InstructionError.InstructionIndex = reader.GetInt32(); + + reader.Read(); + + if (reader.TokenType == JsonTokenType.String) + { + var enumValue = reader.GetString(); + + Enum.TryParse(enumValue, ignoreCase: false, out InstructionErrorType errorType); + err.InstructionError.Type = errorType; + reader.Read(); //string + + reader.Read(); //endarray + return err; + } + + if (reader.TokenType != JsonTokenType.StartObject) + { + throw new JsonException("Unexpected error value."); + } + + reader.Read(); + + + if (reader.TokenType != JsonTokenType.PropertyName) + { + throw new JsonException("Unexpected error value."); + } + { + var enumValue = reader.GetString(); + Enum.TryParse(enumValue, ignoreCase: false, out InstructionErrorType errorType); + err.InstructionError.Type = errorType; + } + + reader.Read(); + + if (reader.TokenType == JsonTokenType.Number) + { + err.InstructionError.CustomError = reader.GetInt32(); + reader.Read(); //number + reader.Read(); //endobj + reader.Read(); //endarray + + return err; + } + + if (reader.TokenType != JsonTokenType.String) + { + throw new JsonException("Unexpected error value."); + } + + err.InstructionError.BorshIoError = reader.GetString(); + reader.Read(); //string + reader.Read(); //endobj + reader.Read(); //endarray + + return err; + } + + public override void Write(Utf8JsonWriter writer, TransactionError value, JsonSerializerOptions options) + { + throw new NotImplementedException(); + } + } } \ No newline at end of file diff --git a/src/Solnet.Rpc/Models/TransactionInstruction.cs b/src/Solnet.Rpc/Models/TransactionInstruction.cs index 749e04a0..a4396f66 100644 --- a/src/Solnet.Rpc/Models/TransactionInstruction.cs +++ b/src/Solnet.Rpc/Models/TransactionInstruction.cs @@ -1,25 +1,25 @@ -using System.Collections.Generic; - -namespace Solnet.Rpc.Models -{ - /// - /// Represents a transaction instruction. - /// - public class TransactionInstruction - { - /// - /// The program ID associated with the instruction. - /// - public byte[] ProgramId { get; init; } - - /// - /// The keys associated with the instruction. - /// - public IList Keys { get; init; } - - /// - /// The instruction-specific data. - /// - public byte[] Data { get; init; } - } +using System.Collections.Generic; + +namespace Solnet.Rpc.Models +{ + /// + /// Represents a transaction instruction. + /// + public class TransactionInstruction + { + /// + /// The program ID associated with the instruction. + /// + public byte[] ProgramId { get; init; } + + /// + /// The keys associated with the instruction. + /// + public IList Keys { get; init; } + + /// + /// The instruction-specific data. + /// + public byte[] Data { get; init; } + } } \ No newline at end of file diff --git a/src/Solnet.Rpc/Models/Version.cs b/src/Solnet.Rpc/Models/Version.cs index c15565c2..173f5d0b 100644 --- a/src/Solnet.Rpc/Models/Version.cs +++ b/src/Solnet.Rpc/Models/Version.cs @@ -1,22 +1,22 @@ -using System.Text.Json.Serialization; - -namespace Solnet.Rpc.Models -{ - /// - /// Represents the current solana versions running on the node. - /// - public class NodeVersion - { - /// - /// Software version of solana-core. - /// - [JsonPropertyName("solana-core")] - public string SolanaCore { get; set; } - - /// - /// unique identifier of the current software's feature set. - /// - [JsonPropertyName("feature-set")] - public ulong? FeatureSet { get; set; } - } +using System.Text.Json.Serialization; + +namespace Solnet.Rpc.Models +{ + /// + /// Represents the current solana versions running on the node. + /// + public class NodeVersion + { + /// + /// Software version of solana-core. + /// + [JsonPropertyName("solana-core")] + public string SolanaCore { get; set; } + + /// + /// unique identifier of the current software's feature set. + /// + [JsonPropertyName("feature-set")] + public ulong? FeatureSet { get; set; } + } } \ No newline at end of file diff --git a/src/Solnet.Rpc/Models/VoteAccount.cs b/src/Solnet.Rpc/Models/VoteAccount.cs index 48648880..e24b47b3 100644 --- a/src/Solnet.Rpc/Models/VoteAccount.cs +++ b/src/Solnet.Rpc/Models/VoteAccount.cs @@ -1,71 +1,71 @@ -using System.Text.Json.Serialization; - -namespace Solnet.Rpc.Models -{ - /// - /// Represents the account info and associated stake for all the voting accounts in the current bank. - /// - public class VoteAccount - { - /// - /// The root slot for this vote account. - /// - public ulong RootSlot { get; set; } - - /// - /// The vote account address, as a base-58 encoded string. - /// - [JsonPropertyName("votePubkey")] - public string VotePublicKey { get; set; } - - /// - /// The validator identity, as a base-58 encoded string. - /// - [JsonPropertyName("nodePubkey")] - public string NodePublicKey { get; set; } - - /// - /// The stake, in lamports, delegated to this vote account and active in this epoch. - /// - public ulong ActivatedStake { get; set; } - - /// - /// Whether the vote account is staked for this epoch. - /// - public bool EpochVoteAccount { get; set; } - - /// - /// Percentage of rewards payout owed to the vote account. - /// - public decimal Commission { get; set; } - - /// - /// Most recent slot voted on by this vote account. - /// - public ulong LastVote { get; set; } - - /// - /// History of how many credits earned by the end of the each epoch. - /// - /// Each array contains [epoch, credits, previousCredits]; - /// - /// - public ulong[][] EpochCredits { get; set; } - } - - /// - /// Represents the vote accounts. - /// - public class VoteAccounts - { - /// - /// Current vote accounts. - /// - public VoteAccount[] Current { get; set; } - - /// - /// Delinquent vote accounts. - /// - public VoteAccount[] Delinquent { get; set; } - } +using System.Text.Json.Serialization; + +namespace Solnet.Rpc.Models +{ + /// + /// Represents the account info and associated stake for all the voting accounts in the current bank. + /// + public class VoteAccount + { + /// + /// The root slot for this vote account. + /// + public ulong RootSlot { get; set; } + + /// + /// The vote account address, as a base-58 encoded string. + /// + [JsonPropertyName("votePubkey")] + public string VotePublicKey { get; set; } + + /// + /// The validator identity, as a base-58 encoded string. + /// + [JsonPropertyName("nodePubkey")] + public string NodePublicKey { get; set; } + + /// + /// The stake, in lamports, delegated to this vote account and active in this epoch. + /// + public ulong ActivatedStake { get; set; } + + /// + /// Whether the vote account is staked for this epoch. + /// + public bool EpochVoteAccount { get; set; } + + /// + /// Percentage of rewards payout owed to the vote account. + /// + public decimal Commission { get; set; } + + /// + /// Most recent slot voted on by this vote account. + /// + public ulong LastVote { get; set; } + + /// + /// History of how many credits earned by the end of the each epoch. + /// + /// Each array contains [epoch, credits, previousCredits]; + /// + /// + public ulong[][] EpochCredits { get; set; } + } + + /// + /// Represents the vote accounts. + /// + public class VoteAccounts + { + /// + /// Current vote accounts. + /// + public VoteAccount[] Current { get; set; } + + /// + /// Delinquent vote accounts. + /// + public VoteAccount[] Delinquent { get; set; } + } } \ No newline at end of file diff --git a/src/Solnet.Rpc/SolanaRpcClient.cs b/src/Solnet.Rpc/SolanaRpcClient.cs index 765c24c4..e18a777b 100644 --- a/src/Solnet.Rpc/SolanaRpcClient.cs +++ b/src/Solnet.Rpc/SolanaRpcClient.cs @@ -1,1017 +1,1017 @@ -using Microsoft.Extensions.Logging; -using Solnet.Rpc.Core; -using Solnet.Rpc.Core.Http; -using Solnet.Rpc.Messages; -using Solnet.Rpc.Models; -using Solnet.Rpc.Types; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; -using System.Threading.Tasks; - -namespace Solnet.Rpc -{ - /// - /// Implements functionality to interact with the Solana JSON RPC API. - public class SolanaRpcClient : JsonRpcClient, IRpcClient - { - /// - /// Message Id generator. - /// - private readonly IdGenerator _idGenerator = new IdGenerator(); - - /// - /// Initialize the Rpc Client with the passed url. - /// - /// The url of the node exposing the JSON RPC API. - /// The logger to use. - /// An http client. - internal SolanaRpcClient(string url, ILogger logger, HttpClient httpClient = default) : base(url, logger, - httpClient) - { - } - - #region RequestBuilder - - /// - /// Build the request for the passed RPC method and parameters. - /// - /// The request's RPC method. - /// A list of parameters to include in the request. - /// The type of the request result. - /// A task which may return a request result. - private JsonRpcRequest BuildRequest(string method, IList parameters) - => new JsonRpcRequest(_idGenerator.GetNextId(), method, parameters); - - /// - /// - /// - /// The request's RPC method. - /// The type of the request result. - /// A task which may return a request result. - private async Task> SendRequestAsync(string method) - { - var req = BuildRequest(method, null); - return await SendRequest(req); - } - - /// - /// Send a request asynchronously. - /// - /// The request's RPC method. - /// A list of parameters to include in the request. - /// The type of the request result. - /// A task which may return a request result. - private async Task> SendRequestAsync(string method, IList parameters) - { - var req = BuildRequest(method, parameters); - return await SendRequest(req); - } - - /// - /// Send the request asynchronously. - /// - /// The request's RPC method. - /// A list of parameters to include in the request. - /// Optional parameters to include in the request. - /// The type of the request result. - /// A task which may return a request result. - private async Task> SendRequestAsync( - string method, IList parameters, Dictionary configurationObject - ) - { - var newList = parameters.ToList(); - - if (configurationObject == null) - { - configurationObject = new Dictionary - { - {"encoding", "jsonParsed"} - }; - } - - foreach (var key in configurationObject.Keys) - { - var ok = configurationObject.TryGetValue(key, out var value); - if (!ok) continue; - - newList.Add(new Dictionary - { - {key, value} - }); - } - - var req = BuildRequest(method, newList); - return await SendRequest(req); - } - - #endregion - - #region Accounts - /// - public async Task>> GetAccountInfoAsync(string pubKey, Commitment commitment = Commitment.Finalized) - { - var configParams = new Dictionary { { "encoding", "base64" } }; - - // given that finalized is the default, lets not add it to speed things up - if (commitment != Commitment.Finalized) - { - configParams.Add("commitment", commitment); - } - - return await SendRequestAsync>("getAccountInfo", new List { pubKey, configParams }); - } - - /// - public RequestResult> GetAccountInfo(string pubKey, Commitment commitment = Commitment.Finalized) - => GetAccountInfoAsync(pubKey, commitment).Result; - - - /// - public async Task>> GetProgramAccountsAsync(string pubKey) - { - return await SendRequestAsync>("getProgramAccounts", - new List { pubKey }, - new Dictionary - { - { - "encoding", "base64" - } - }); - } - - /// - public RequestResult> GetProgramAccounts(string pubKey) - => GetProgramAccountsAsync(pubKey).Result; - - - /// - public async Task>>> GetMultipleAccountsAsync(IList accounts) - { - return await SendRequestAsync>>("getMultipleAccounts", new List { accounts, - new Dictionary { { "encoding", "base64" } } }); - } - - /// - public RequestResult>> GetMultipleAccounts(IList accounts) - => GetMultipleAccountsAsync(accounts).Result; - - #endregion - - /// - public async Task>> GetBalanceAsync(string pubKey, Commitment commitment = Commitment.Finalized) - { - var paramsList = new List { pubKey }; - - if (commitment != Commitment.Finalized) - { - paramsList.Add(new Dictionary { { "commitment", commitment } }); - } - - return await SendRequestAsync>("getBalance", paramsList); - } - - /// - public RequestResult> GetBalance(string pubKey, Commitment commitment = Commitment.Finalized) - => GetBalanceAsync(pubKey, commitment).Result; - - #region Blocks - /// - public async Task> GetBlockAsync(ulong slot, Commitment commitment = Commitment.Finalized) - { - // transaction details default is aready full - var configParams = new Dictionary { { "encoding", "json" } }; - - if (commitment != Commitment.Finalized) - { - configParams.Add("commitment", commitment); - } - - return await SendRequestAsync("getBlock", new List { slot, configParams }); - } - - /// - public RequestResult GetBlock(ulong slot, Commitment commitment = Commitment.Finalized) - => GetBlockAsync(slot).Result; - - - /// - public async Task>> GetBlocksAsync(ulong startSlot, ulong endSlot = 0) - { - var parameters = new List { startSlot }; - if (endSlot > 0) parameters.Add(endSlot); - - return await SendRequestAsync>("getBlocks", parameters); - } - - /// - public RequestResult> GetBlocks(ulong startSlot, ulong endSlot = 0) - => GetBlocksAsync(startSlot, endSlot).Result; - - - /// - public RequestResult> GetBlocksWithLimit(ulong startSlot, ulong limit) - => GetBlocksWithLimitAsync(startSlot, limit).Result; - - /// - public async Task>> GetBlocksWithLimitAsync(ulong startSlot, ulong limit) - { - return await SendRequestAsync>("getBlocksWithLimit", new List { startSlot, limit }); - } - - - /// - public RequestResult GetFirstAvailableBlock() - => GetFirstAvailableBlockAsync().Result; - - /// - public async Task> GetFirstAvailableBlockAsync() - { - return await SendRequestAsync("getFirstAvailableBlock"); - } - #endregion - - #region Block Production - /// - public async Task>> GetBlockProductionAsync() - { - return await SendRequestAsync>("getBlockProduction"); - } - - /// - public RequestResult> GetBlockProduction() => GetBlockProductionAsync().Result; - - /// - public async Task>> GetBlockProductionAsync(ulong firstSlot, ulong lastSlot = 0) - { - var range = new Dictionary { { "firstSlot", firstSlot } }; - if (lastSlot != 0) - { - range.Add("lastSlot", lastSlot); - } - - return await SendRequestAsync>("getBlockProduction", new List - { - new Dictionary - { - { "range", range } - } - }); - } - - /// - public RequestResult> GetBlockProduction(ulong firstSlot, ulong lastSlot = 0) - => GetBlockProductionAsync(firstSlot, lastSlot).Result; - - /// - public async Task>> GetBlockProductionAsync(string identity) - { - return await SendRequestAsync>("getBlockProduction", - new List { new Dictionary { { "identity", identity } } }); - } - - /// - public RequestResult> GetBlockProduction(string identity) - => GetBlockProductionAsync(identity).Result; - - /// - public async Task>> GetBlockProductionAsync(string identity, ulong firstSlot, ulong lastSlot = 0) - { - var range = new Dictionary { { "firstSlot", firstSlot } }; - if (lastSlot != 0) - { - range.Add("lastSlot", lastSlot); - } - - return await SendRequestAsync>("getBlockProduction", new List - { - new Dictionary - { - { "identity", identity }, - { "range", range } - } - }); - } - - /// - public RequestResult> GetBlockProduction(string identity, ulong firstSlot, ulong lastSlot = 0) - => GetBlockProductionAsync(identity, firstSlot, lastSlot).Result; - #endregion - - /// - public RequestResult GetHealth() - => GetHealthAsync().Result; - - /// - public async Task> GetHealthAsync() - { - return await SendRequestAsync("getHealth"); - } - - - /// - public RequestResult>> GetLeaderSchedule(ulong slot = 0, string identity = null) - => GetLeaderScheduleAsync(slot, identity).Result; - - /// - public async Task>>> GetLeaderScheduleAsync(ulong slot = 0, string identity = null) - { - List parameters = new List(); - - if (slot > 0) parameters.Add(slot); - if (identity != null) - { - if (slot == 0) parameters.Add(null); - parameters.Add(new Dictionary { { "identity", identity } }); - } - - return await SendRequestAsync>>("getLeaderSchedule", parameters.Count > 0 ? parameters : null); - } - - - /// - public async Task> GetTransactionAsync(string signature) - { - return await SendRequestAsync("getTransaction", new List { signature, "json" }); - } - - /// - public RequestResult GetTransaction(string signature) - => GetTransactionAsync(signature).Result; - - - /// - /// Gets the current block height of the node. - /// - /// A task which may return a request result and a block height. - public async Task> GetBlockHeightAsync() - { - return await SendRequestAsync("getBlockHeight"); - } - - /// - public RequestResult GetBlockHeight() - => GetBlockHeightAsync().Result; - - /// - /// Gets the block commitment of a certain block, identified by slot. - /// - /// The slot. - /// A task which may return a request result and block commitment information. - public async Task> GetBlockCommitmentAsync(ulong slot) - { - return await SendRequestAsync("getBlockCommitment", new List { slot }); - } - - /// - public RequestResult GetBlockCommitment(ulong slot) - => GetBlockCommitmentAsync(slot).Result; - - /// - /// Gets the estimated production time for a certain block, identified by slot. - /// - /// The slot. - /// A task which may return a request result and block production time as Unix timestamp (seconds since Epoch). - public async Task> GetBlockTimeAsync(ulong slot) - { - return await SendRequestAsync("getBlockTime", new List { slot }); - } - - /// - public RequestResult GetBlockTime(ulong slot) - => GetBlockTimeAsync(slot).Result; - - /// - /// Gets the cluster nodes. - /// - /// A task which may return a request result and information about the nodes participating in the cluster. - public async Task>> GetClusterNodesAsync() - { - return await SendRequestAsync>("getClusterNodes"); - } - - /// - public RequestResult> GetClusterNodes() - => GetClusterNodesAsync().Result; - - /// - /// Gets information about the current epoch. - /// - /// A task which may return a request result and information about the current epoch. - public async Task> GetEpochInfoAsync() - { - return await SendRequestAsync("getEpochInfo"); - } - - /// - public RequestResult GetEpochInfo() => GetEpochInfoAsync().Result; - - /// - /// Gets epoch schedule information from this cluster's genesis config. - /// - /// A task which may return a request result and epoch schedule information from this cluster's genesis config. - public async Task> GetEpochScheduleAsync() - { - return await SendRequestAsync("getEpochSchedule"); - } - - /// - public RequestResult GetEpochSchedule() => GetEpochScheduleAsync().Result; - - /// - /// Gets the fee calculator associated with the query blockhash, or null if the blockhash has expired. - /// - /// The blockhash to query, as base-58 encoded string. - /// A task which may return a request result and the fee calculator for the block. - public async Task>> GetFeeCalculatorForBlockhashAsync( - string blockhash) - { - return await SendRequestAsync>("getFeeCalculatorForBlockhash", - new List { blockhash }); - } - - /// - public RequestResult> GetFeeCalculatorForBlockhash(string blockhash) => - GetFeeCalculatorForBlockhashAsync(blockhash).Result; - - /// - /// Gets the fee rate governor information from the root bank. - /// - /// A task which may return a request result and the fee rate governor. - public async Task>> GetFeeRateGovernorAsync() - { - return await SendRequestAsync>("getFeeRateGovernor"); - } - - /// - public RequestResult> GetFeeRateGovernor() - => GetFeeRateGovernorAsync().Result; - - /// - /// Gets a recent block hash from the ledger, a fee schedule that can be used to compute the - /// cost of submitting a transaction using it, and the last slot in which the blockhash will be valid. - /// - /// A task which may return a request result and information about fees. - public async Task>> GetFeesAsync() - { - return await SendRequestAsync>("getFees"); - } - - /// - public RequestResult> GetFees() => GetFeesAsync().Result; - - /// - /// Gets a recent block hash. - /// - /// A task which may return a request result and recent block hash. - public async Task>> GetRecentBlockHashAsync() - { - return await SendRequestAsync>("getRecentBlockhash"); - } - - /// - public RequestResult> GetRecentBlockHash() - => GetRecentBlockHashAsync().Result; - - /// - /// Gets the maximum slot seen from retransmit stage. - /// - /// A task which may return a request result the maximum slot. - public async Task> GetMaxRetransmitSlotAsync() - { - return await SendRequestAsync("getMaxRetransmitSlot"); - } - - /// - public RequestResult GetMaxRetransmitSlot() - => GetMaxRetransmitSlotAsync().Result; - - /// - /// Gets the maximum slot seen from after shred insert. - /// - /// A task which may return a request result the maximum slot. - public async Task> GetMaxShredInsertSlotAsync() - { - return await SendRequestAsync("getMaxShredInsertSlot"); - } - - /// - public RequestResult GetMaxShredInsertSlot() - => GetMaxShredInsertSlotAsync().Result; - - /// - /// Gets the minimum balance required to make account rent exempt. - /// - /// The account data size. - /// A task which may return a request result and the rent exemption value. - public async Task> GetMinimumBalanceForRentExemptionAsync(long accountDataSize) - { - return await SendRequestAsync("getMinimumBalanceForRentExemption", - new List { accountDataSize }); - } - - /// - public RequestResult GetMinimumBalanceForRentExemption(long accountDataSize) - => GetMinimumBalanceForRentExemptionAsync(accountDataSize).Result; - - /// - /// Gets the genesis hash of the ledger. - /// - /// A task which may return a request result and a string representing the genesis hash. - public async Task> GetGenesisHashAsync() - { - return await SendRequestAsync("getGenesisHash"); - } - - /// - public RequestResult GetGenesisHash() - => GetGenesisHashAsync().Result; - - /// - /// Gets the identity pubkey for the current node. - /// - /// A task which may return a request result and a string representing the genesis hash. - public async Task> GetIdentityAsync() - { - return await SendRequestAsync("getIdentity"); - } - - /// - public RequestResult GetIdentity() - => GetIdentityAsync().Result; - - /// - /// Gets the current inflation governor. - /// - /// A task which may return a request result and an object representing the current inflation governor. - public async Task> GetInflationGovernorAsync() - { - return await SendRequestAsync("getInflationGovernor"); - } - - /// - public RequestResult GetInflationGovernor() - => GetInflationGovernorAsync().Result; - - /// - /// Gets the specific inflation values for the current epoch. - /// - /// A task which may return a request result and an object representing the current inflation rate. - public async Task> GetInflationRateAsync() - { - return await SendRequestAsync("getInflationRate"); - } - - /// - public RequestResult GetInflationRate() - => GetInflationRateAsync().Result; - - /// - /// Gets the inflation reward for a list of addresses for an epoch. - /// - /// The list of addresses to query for, as base-58 encoded strings. - /// The epoch. - /// A task which may return a request result and a list of objects representing the inflation reward. - public async Task>> GetInflationRewardAsync(List addresses, ulong epoch = 0) - { - if (epoch != 0) - return await SendRequestAsync>("getInflationReward", - new List { addresses, epoch }); - return await SendRequestAsync>("getInflationReward", new List { addresses }); - } - - /// - public RequestResult> GetInflationReward(List addresses, ulong epoch = 0) - => GetInflationRewardAsync(addresses, epoch).Result; - - /// - /// Gets the 20 largest accounts, by lamport balance. - /// - /// Filter results by account type. Available types: circulating/nonCirculating - /// A task which may return a request result the current slot. - /// Results may be cached up to two hours. - public async Task>>> GetLargestAccountsAsync(string filter) - { - return await SendRequestAsync>>("getLargestAccounts", - new List { new Dictionary { { "filter", filter } } }); - } - - /// - public RequestResult>> GetLargestAccounts(string filter) => - GetLargestAccountsAsync(filter).Result; - - /// - /// Gets the highest slot that the node has a snapshot for. - /// - /// A task which may return a request result the highest slot with a snapshot. - public async Task> GetSnapshotSlotAsync() - { - return await SendRequestAsync("getSnapshotSlot"); - } - - /// - public RequestResult GetSnapshotSlot() => GetSnapshotSlotAsync().Result; - - /// - /// Gets a list of recent performance samples. - /// - /// Unless searchTransactionHistory is included, this method only searches the recent status cache of signatures. - /// - /// - /// Maximum transaction signatures to return, between 1-720. Default is 720. - /// A task which may return a request result the signatures for the transactions. - public async Task>> GetRecentPerformanceSamplesAsync(ulong limit = 720) - { - return await SendRequestAsync>("getRecentPerformanceSamples", - new List { limit }); - } - - /// - public RequestResult> GetRecentPerformanceSamples(ulong limit = 720) - => GetRecentPerformanceSamplesAsync(limit).Result; - - /// - /// Gets confirmed signatures for transactions involving the address. - /// - /// Unless searchTransactionHistory is included, this method only searches the recent status cache of signatures. - /// - /// - /// The account address as base-58 encoded string. - /// Maximum transaction signatures to return, between 1-1000. Default is 1000. - /// Start searching backwards from this transaction signature. - /// Search until this transaction signature, if found before limit is reached. - /// A task which may return a request result the signatures for the transactions. - public async Task>> GetSignaturesForAddressAsync( - string accountPubKey, ulong limit = 1000, string before = "", string until = "") - { - var dictionary = new Dictionary { { "limit", limit } }; - - if (!string.IsNullOrWhiteSpace(before)) - dictionary.Add("before", before); - - if (!string.IsNullOrWhiteSpace(until)) - dictionary.Add("until", until); - - return await SendRequestAsync>("getSignaturesForAddress", - new List { accountPubKey, dictionary }); - } - - /// - public RequestResult> GetSignaturesForAddress( - string accountPubKey, ulong limit = 1000, string before = "", string until = "") - => GetSignaturesForAddressAsync(accountPubKey, limit, before, until).Result; - - /// - /// Gets the status of a list of signatures. - /// - /// Unless searchTransactionHistory is included, this method only searches the recent status cache of signatures. - /// - /// - /// The list of transactions to search status info for. - /// If the node should search for signatures in it's ledger cache. - /// A task which may return a request result the highest slot with a snapshot. - public async Task>>> GetSignatureStatusesAsync(List transactionHashes, bool searchTransactionHistory = false) - { - if (searchTransactionHistory) - return await SendRequestAsync>>("getSignatureStatuses", - new List { transactionHashes, new Dictionary { { "searchTransactionHistory", true } } }); - return await SendRequestAsync>>("getSignatureStatuses", - new List { transactionHashes }); - } - - /// - public RequestResult>> GetSignatureStatuses(List transactionHashes, - bool searchTransactionHistory = false) - => GetSignatureStatusesAsync(transactionHashes, searchTransactionHistory).Result; - - /// - /// Gets the current slot the node is processing - /// - /// A task which may return a request result the current slot. - public async Task> GetSlotAsync() - { - return await SendRequestAsync("getSlot"); - } - - /// - public RequestResult GetSlot() => GetSlotAsync().Result; - - /// - /// Gets the current slot leader. - /// - /// A task which may return a request result the slot leader. - public async Task> GetSlotLeaderAsync() - { - return await SendRequestAsync("getSlotLeader"); - } - - /// - public RequestResult GetSlotLeader() => GetSlotLeaderAsync().Result; - - /// - /// Gets the slot leaders for a given slot range. - /// - /// The start slot. - /// The result limit. - /// A task which may return a request result and a list slot leaders. - public async Task>> GetSlotLeadersAsync(ulong start, ulong limit) - { - return await SendRequestAsync>("getSlotLeaders", new List { start, limit }); - } - - /// - public RequestResult> GetSlotLeaders(ulong start, ulong limit) - => GetSlotLeadersAsync(start, limit).Result; - - #region Token Supply and Balances - - /// - /// Gets the epoch activation information for a stake account. - /// - /// Public key of account to query, as base-58 encoded string - /// Epoch for which to calculate activation details. - /// A task which may return a request result and information about the stake activation. - public async Task> GetStakeActivationAsync(string publicKey, ulong epoch = 0) - { - if (epoch != 0) - return await SendRequestAsync("getStakeActivation", - new List { publicKey, new Dictionary { { "epoch", epoch } } }); - return await SendRequestAsync("getStakeActivation", - new List { publicKey }); - } - - /// - public RequestResult GetStakeActivation(string publicKey, ulong epoch = 0) => - GetStakeActivationAsync(publicKey, epoch).Result; - - /// - /// Gets information about the current supply. - /// - /// A task which may return a request result and information about the supply. - public async Task>> GetSupplyAsync() - { - return await SendRequestAsync>("getSupply"); - } - - /// - public RequestResult> GetSupply() - => GetSupplyAsync().Result; - - /// - /// Gets the token balance of an SPL Token account. - /// - /// Public key of Token account to query, as base-58 encoded string. - /// A task which may return a request result and information about token account balance. - public async Task>> GetTokenAccountBalanceAsync( - string splTokenAccountPublicKey) - { - return await SendRequestAsync>("getTokenAccountBalance", - new List { splTokenAccountPublicKey }); - } - - /// - public RequestResult> GetTokenAccountBalance(string splTokenAccountPublicKey) - => GetTokenAccountBalanceAsync(splTokenAccountPublicKey).Result; - - /// - /// Gets all SPL Token accounts by approved delegate. - /// - /// Public key of account owner query, as base-58 encoded string. - /// Public key of the specific token Mint to limit accounts to, as base-58 encoded string. - /// Public key of the Token program ID that owns the accounts, as base-58 encoded string. - /// A task which may return a request result and information about token accounts by delegate. - public async Task>>> GetTokenAccountsByDelegateAsync( - string ownerPubKey, string tokenMintPubKey = "", string tokenProgramId = "") - { - if (string.IsNullOrWhiteSpace(tokenMintPubKey) && string.IsNullOrWhiteSpace(tokenProgramId)) - throw new ArgumentException("either tokenProgramId or tokenMintPubKey must be set"); - var options = new Dictionary(); - if (!string.IsNullOrWhiteSpace(tokenMintPubKey)) options.Add("mint", tokenMintPubKey); - if (!string.IsNullOrWhiteSpace(tokenProgramId)) options.Add("programId", tokenProgramId); - return await SendRequestAsync>>( - "getTokenAccountsByDelegate", - new List { ownerPubKey, options, new Dictionary { { "encoding", "jsonParsed" } } }); - } - - /// - public RequestResult>> GetTokenAccountsByDelegate( - string ownerPubKey, string tokenMintPubKey = "", string tokenProgramId = "") - => GetTokenAccountsByDelegateAsync(ownerPubKey, tokenMintPubKey, tokenProgramId).Result; - - /// - /// Gets all SPL Token accounts by token owner. - /// - /// Public key of account owner query, as base-58 encoded string. - /// Public key of the specific token Mint to limit accounts to, as base-58 encoded string. - /// Public key of the Token program ID that owns the accounts, as base-58 encoded string. - /// A task which may return a request result and information about token accounts by owner. - public async Task>>> GetTokenAccountsByOwnerAsync( - string ownerPubKey, string tokenMintPubKey = "", string tokenProgramId = "") - { - if (string.IsNullOrWhiteSpace(tokenMintPubKey) && string.IsNullOrWhiteSpace(tokenProgramId)) - throw new ArgumentException("either tokenProgramId or tokenMintPubKey must be set"); - var options = new Dictionary(); - if (!string.IsNullOrWhiteSpace(tokenMintPubKey)) options.Add("mint", tokenMintPubKey); - if (!string.IsNullOrWhiteSpace(tokenProgramId)) options.Add("programId", tokenProgramId); - return await SendRequestAsync>>( - "getTokenAccountsByOwner", - new List { ownerPubKey, options, new Dictionary { { "encoding", "jsonParsed" } } }); - } - - /// - public RequestResult>> GetTokenAccountsByOwner( - string ownerPubKey, string tokenMintPubKey = "", string tokenProgramId = "") - => GetTokenAccountsByOwnerAsync(ownerPubKey, tokenMintPubKey, tokenProgramId).Result; - - /// - /// Gets the 20 largest accounts of a particular SPL Token. - /// - /// Public key of Token Mint to query, as base-58 encoded string. - /// A task which may return a request result and information about largest accounts. - public async Task>>> GetTokenLargestAccountsAsync( - string tokenMintPubKey) - { - return await SendRequestAsync>>("getTokenLargestAccounts", - new List { tokenMintPubKey }); - } - - /// - public RequestResult>> GetTokenLargestAccounts(string tokenMintPubKey) - => GetTokenLargestAccountsAsync(tokenMintPubKey).Result; - - /// - /// Get the token supply of an SPL Token type. - /// - /// Public key of Token Mint to query, as base-58 encoded string. - /// A task which may return a request result and information about the supply. - public async Task>> GetTokenSupplyAsync(string tokenMintPubKey) - { - return await SendRequestAsync>("getTokenSupply", - new List { tokenMintPubKey }); - } - - /// - public RequestResult> GetTokenSupply(string tokenMintPubKey) - => GetTokenSupplyAsync(tokenMintPubKey).Result; - - #endregion - - /// - /// Gets the total transaction count of the ledger. - /// - /// A task which may return a request result and information about the transaction count. - public async Task> GetTransactionCountAsync() - { - return await SendRequestAsync("getTransactionCount"); - } - - /// - public RequestResult GetTransactionCount() - => GetTransactionCountAsync().Result; - - /// - /// Gets the current node's software version. - /// - /// A task which may return a request result and information about the node's software version. - public async Task> GetVersionAsync() - { - return await SendRequestAsync("getVersion"); - } - - /// - public RequestResult GetVersion() - => GetVersionAsync().Result; - - /// - /// Gets the account info and associated stake for all voting accounts in the current bank. - /// - /// A task which may return a request result and information about the vote accounts. - public async Task> GetVoteAccountsAsync() - { - return await SendRequestAsync("getVoteAccounts"); - } - - /// - public RequestResult GetVoteAccounts() - => GetVoteAccountsAsync().Result; - - /// - /// Gets the lowest slot that the node has information about in its ledger. - /// - /// This value may decrease over time if a node is configured to purging data. - /// - /// - /// A task which may return a request result and the minimum slot available. - public async Task> GetMinimumLedgerSlotAsync() - { - return await SendRequestAsync("minimumLedgerSlot"); - } - - /// - public RequestResult GetMinimumLedgerSlot() - => GetMinimumLedgerSlotAsync().Result; - - /// - /// Requests an airdrop to the passed pubKey of the passed lamports amount. - /// - /// The commitment parameter is optional, the default is used. - /// - /// - /// The public key of to receive the airdrop. - /// The amount of lamports to request. - /// The block commitment used to retrieve block hashes and verify success. - /// A task which may return a request result and the transaction signature of the airdrop, as base-58 encoded string.. - public async Task> RequestAirdropAsync(string pubKey, ulong lamports, - Commitment commitment = Commitment.Finalized) - { - return await SendRequestAsync("requestAirdrop", new List { pubKey, lamports, commitment }); - } - - /// - public RequestResult RequestAirdrop(string pubKey, ulong lamports, - Commitment commitment = Commitment.Finalized) - => RequestAirdropAsync(pubKey, lamports, commitment).Result; - - #region Transactions - - /// - /// Sends a transaction. - /// - /// The signed transaction as base-58 or base-64 encoded string. - /// The encoding of the transaction. - /// - /// A task which may return a request result and the first transaction signature embedded in the transaction, as base-58 encoded string. - /// - public async Task> SendTransactionAsync(string transaction, - BinaryEncoding encoding = BinaryEncoding.Base64) - { - return await SendRequestAsync("sendTransaction", - new List - { - transaction - }, - new Dictionary - { - { - "encoding", encoding - } - }); - } - - /// - public RequestResult SendTransaction(string transaction, - BinaryEncoding encoding = BinaryEncoding.Base64) - => SendTransactionAsync(transaction, encoding).Result; - - /// - /// Sends a transaction. - /// - /// The signed transaction as byte array. - /// - /// A task which may return a request result and the first transaction signature embedded in the transaction, as base-58 encoded string. - /// - public RequestResult SendTransaction(byte[] transaction) - => SendTransactionAsync(Convert.ToBase64String(transaction)).Result; - - /// - /// Simulate sending a transaction. - /// - /// The signed transaction as a byte array. - /// The encoding of the transaction. - /// - /// A task which may return a request result and the transaction status. - /// - public async Task>> SimulateTransactionAsync(string transaction, - BinaryEncoding encoding = BinaryEncoding.Base64) - { - return await SendRequestAsync>("simulateTransaction", - new List - { - transaction - }, new Dictionary - { - { - "encoding", encoding - } - }); - } - - /// - public RequestResult> SimulateTransaction(string transaction, - BinaryEncoding encoding = BinaryEncoding.Base64) - => SimulateTransactionAsync(transaction, encoding).Result; - - /// - /// Simulate sending a transaction. - /// - /// The signed transaction as a byte array. - /// - /// A task which may return a request result and the transaction status. - /// - public RequestResult> SimulateTransaction(byte[] transaction) - => SimulateTransactionAsync(Convert.ToBase64String(transaction)).Result; - - #endregion - } +using Microsoft.Extensions.Logging; +using Solnet.Rpc.Core; +using Solnet.Rpc.Core.Http; +using Solnet.Rpc.Messages; +using Solnet.Rpc.Models; +using Solnet.Rpc.Types; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; + +namespace Solnet.Rpc +{ + /// + /// Implements functionality to interact with the Solana JSON RPC API. + public class SolanaRpcClient : JsonRpcClient, IRpcClient + { + /// + /// Message Id generator. + /// + private readonly IdGenerator _idGenerator = new IdGenerator(); + + /// + /// Initialize the Rpc Client with the passed url. + /// + /// The url of the node exposing the JSON RPC API. + /// The logger to use. + /// An http client. + internal SolanaRpcClient(string url, ILogger logger, HttpClient httpClient = default) : base(url, logger, + httpClient) + { + } + + #region RequestBuilder + + /// + /// Build the request for the passed RPC method and parameters. + /// + /// The request's RPC method. + /// A list of parameters to include in the request. + /// The type of the request result. + /// A task which may return a request result. + private JsonRpcRequest BuildRequest(string method, IList parameters) + => new JsonRpcRequest(_idGenerator.GetNextId(), method, parameters); + + /// + /// + /// + /// The request's RPC method. + /// The type of the request result. + /// A task which may return a request result. + private async Task> SendRequestAsync(string method) + { + var req = BuildRequest(method, null); + return await SendRequest(req); + } + + /// + /// Send a request asynchronously. + /// + /// The request's RPC method. + /// A list of parameters to include in the request. + /// The type of the request result. + /// A task which may return a request result. + private async Task> SendRequestAsync(string method, IList parameters) + { + var req = BuildRequest(method, parameters); + return await SendRequest(req); + } + + /// + /// Send the request asynchronously. + /// + /// The request's RPC method. + /// A list of parameters to include in the request. + /// Optional parameters to include in the request. + /// The type of the request result. + /// A task which may return a request result. + private async Task> SendRequestAsync( + string method, IList parameters, Dictionary configurationObject + ) + { + var newList = parameters.ToList(); + + if (configurationObject == null) + { + configurationObject = new Dictionary + { + {"encoding", "jsonParsed"} + }; + } + + foreach (var key in configurationObject.Keys) + { + var ok = configurationObject.TryGetValue(key, out var value); + if (!ok) continue; + + newList.Add(new Dictionary + { + {key, value} + }); + } + + var req = BuildRequest(method, newList); + return await SendRequest(req); + } + + #endregion + + #region Accounts + /// + public async Task>> GetAccountInfoAsync(string pubKey, Commitment commitment = Commitment.Finalized) + { + var configParams = new Dictionary { { "encoding", "base64" } }; + + // given that finalized is the default, lets not add it to speed things up + if (commitment != Commitment.Finalized) + { + configParams.Add("commitment", commitment); + } + + return await SendRequestAsync>("getAccountInfo", new List { pubKey, configParams }); + } + + /// + public RequestResult> GetAccountInfo(string pubKey, Commitment commitment = Commitment.Finalized) + => GetAccountInfoAsync(pubKey, commitment).Result; + + + /// + public async Task>> GetProgramAccountsAsync(string pubKey) + { + return await SendRequestAsync>("getProgramAccounts", + new List { pubKey }, + new Dictionary + { + { + "encoding", "base64" + } + }); + } + + /// + public RequestResult> GetProgramAccounts(string pubKey) + => GetProgramAccountsAsync(pubKey).Result; + + + /// + public async Task>>> GetMultipleAccountsAsync(IList accounts) + { + return await SendRequestAsync>>("getMultipleAccounts", new List { accounts, + new Dictionary { { "encoding", "base64" } } }); + } + + /// + public RequestResult>> GetMultipleAccounts(IList accounts) + => GetMultipleAccountsAsync(accounts).Result; + + #endregion + + /// + public async Task>> GetBalanceAsync(string pubKey, Commitment commitment = Commitment.Finalized) + { + var paramsList = new List { pubKey }; + + if (commitment != Commitment.Finalized) + { + paramsList.Add(new Dictionary { { "commitment", commitment } }); + } + + return await SendRequestAsync>("getBalance", paramsList); + } + + /// + public RequestResult> GetBalance(string pubKey, Commitment commitment = Commitment.Finalized) + => GetBalanceAsync(pubKey, commitment).Result; + + #region Blocks + /// + public async Task> GetBlockAsync(ulong slot, Commitment commitment = Commitment.Finalized) + { + // transaction details default is aready full + var configParams = new Dictionary { { "encoding", "json" } }; + + if (commitment != Commitment.Finalized) + { + configParams.Add("commitment", commitment); + } + + return await SendRequestAsync("getBlock", new List { slot, configParams }); + } + + /// + public RequestResult GetBlock(ulong slot, Commitment commitment = Commitment.Finalized) + => GetBlockAsync(slot).Result; + + + /// + public async Task>> GetBlocksAsync(ulong startSlot, ulong endSlot = 0) + { + var parameters = new List { startSlot }; + if (endSlot > 0) parameters.Add(endSlot); + + return await SendRequestAsync>("getBlocks", parameters); + } + + /// + public RequestResult> GetBlocks(ulong startSlot, ulong endSlot = 0) + => GetBlocksAsync(startSlot, endSlot).Result; + + + /// + public RequestResult> GetBlocksWithLimit(ulong startSlot, ulong limit) + => GetBlocksWithLimitAsync(startSlot, limit).Result; + + /// + public async Task>> GetBlocksWithLimitAsync(ulong startSlot, ulong limit) + { + return await SendRequestAsync>("getBlocksWithLimit", new List { startSlot, limit }); + } + + + /// + public RequestResult GetFirstAvailableBlock() + => GetFirstAvailableBlockAsync().Result; + + /// + public async Task> GetFirstAvailableBlockAsync() + { + return await SendRequestAsync("getFirstAvailableBlock"); + } + #endregion + + #region Block Production + /// + public async Task>> GetBlockProductionAsync() + { + return await SendRequestAsync>("getBlockProduction"); + } + + /// + public RequestResult> GetBlockProduction() => GetBlockProductionAsync().Result; + + /// + public async Task>> GetBlockProductionAsync(ulong firstSlot, ulong lastSlot = 0) + { + var range = new Dictionary { { "firstSlot", firstSlot } }; + if (lastSlot != 0) + { + range.Add("lastSlot", lastSlot); + } + + return await SendRequestAsync>("getBlockProduction", new List + { + new Dictionary + { + { "range", range } + } + }); + } + + /// + public RequestResult> GetBlockProduction(ulong firstSlot, ulong lastSlot = 0) + => GetBlockProductionAsync(firstSlot, lastSlot).Result; + + /// + public async Task>> GetBlockProductionAsync(string identity) + { + return await SendRequestAsync>("getBlockProduction", + new List { new Dictionary { { "identity", identity } } }); + } + + /// + public RequestResult> GetBlockProduction(string identity) + => GetBlockProductionAsync(identity).Result; + + /// + public async Task>> GetBlockProductionAsync(string identity, ulong firstSlot, ulong lastSlot = 0) + { + var range = new Dictionary { { "firstSlot", firstSlot } }; + if (lastSlot != 0) + { + range.Add("lastSlot", lastSlot); + } + + return await SendRequestAsync>("getBlockProduction", new List + { + new Dictionary + { + { "identity", identity }, + { "range", range } + } + }); + } + + /// + public RequestResult> GetBlockProduction(string identity, ulong firstSlot, ulong lastSlot = 0) + => GetBlockProductionAsync(identity, firstSlot, lastSlot).Result; + #endregion + + /// + public RequestResult GetHealth() + => GetHealthAsync().Result; + + /// + public async Task> GetHealthAsync() + { + return await SendRequestAsync("getHealth"); + } + + + /// + public RequestResult>> GetLeaderSchedule(ulong slot = 0, string identity = null) + => GetLeaderScheduleAsync(slot, identity).Result; + + /// + public async Task>>> GetLeaderScheduleAsync(ulong slot = 0, string identity = null) + { + List parameters = new List(); + + if (slot > 0) parameters.Add(slot); + if (identity != null) + { + if (slot == 0) parameters.Add(null); + parameters.Add(new Dictionary { { "identity", identity } }); + } + + return await SendRequestAsync>>("getLeaderSchedule", parameters.Count > 0 ? parameters : null); + } + + + /// + public async Task> GetTransactionAsync(string signature) + { + return await SendRequestAsync("getTransaction", new List { signature, "json" }); + } + + /// + public RequestResult GetTransaction(string signature) + => GetTransactionAsync(signature).Result; + + + /// + /// Gets the current block height of the node. + /// + /// A task which may return a request result and a block height. + public async Task> GetBlockHeightAsync() + { + return await SendRequestAsync("getBlockHeight"); + } + + /// + public RequestResult GetBlockHeight() + => GetBlockHeightAsync().Result; + + /// + /// Gets the block commitment of a certain block, identified by slot. + /// + /// The slot. + /// A task which may return a request result and block commitment information. + public async Task> GetBlockCommitmentAsync(ulong slot) + { + return await SendRequestAsync("getBlockCommitment", new List { slot }); + } + + /// + public RequestResult GetBlockCommitment(ulong slot) + => GetBlockCommitmentAsync(slot).Result; + + /// + /// Gets the estimated production time for a certain block, identified by slot. + /// + /// The slot. + /// A task which may return a request result and block production time as Unix timestamp (seconds since Epoch). + public async Task> GetBlockTimeAsync(ulong slot) + { + return await SendRequestAsync("getBlockTime", new List { slot }); + } + + /// + public RequestResult GetBlockTime(ulong slot) + => GetBlockTimeAsync(slot).Result; + + /// + /// Gets the cluster nodes. + /// + /// A task which may return a request result and information about the nodes participating in the cluster. + public async Task>> GetClusterNodesAsync() + { + return await SendRequestAsync>("getClusterNodes"); + } + + /// + public RequestResult> GetClusterNodes() + => GetClusterNodesAsync().Result; + + /// + /// Gets information about the current epoch. + /// + /// A task which may return a request result and information about the current epoch. + public async Task> GetEpochInfoAsync() + { + return await SendRequestAsync("getEpochInfo"); + } + + /// + public RequestResult GetEpochInfo() => GetEpochInfoAsync().Result; + + /// + /// Gets epoch schedule information from this cluster's genesis config. + /// + /// A task which may return a request result and epoch schedule information from this cluster's genesis config. + public async Task> GetEpochScheduleAsync() + { + return await SendRequestAsync("getEpochSchedule"); + } + + /// + public RequestResult GetEpochSchedule() => GetEpochScheduleAsync().Result; + + /// + /// Gets the fee calculator associated with the query blockhash, or null if the blockhash has expired. + /// + /// The blockhash to query, as base-58 encoded string. + /// A task which may return a request result and the fee calculator for the block. + public async Task>> GetFeeCalculatorForBlockhashAsync( + string blockhash) + { + return await SendRequestAsync>("getFeeCalculatorForBlockhash", + new List { blockhash }); + } + + /// + public RequestResult> GetFeeCalculatorForBlockhash(string blockhash) => + GetFeeCalculatorForBlockhashAsync(blockhash).Result; + + /// + /// Gets the fee rate governor information from the root bank. + /// + /// A task which may return a request result and the fee rate governor. + public async Task>> GetFeeRateGovernorAsync() + { + return await SendRequestAsync>("getFeeRateGovernor"); + } + + /// + public RequestResult> GetFeeRateGovernor() + => GetFeeRateGovernorAsync().Result; + + /// + /// Gets a recent block hash from the ledger, a fee schedule that can be used to compute the + /// cost of submitting a transaction using it, and the last slot in which the blockhash will be valid. + /// + /// A task which may return a request result and information about fees. + public async Task>> GetFeesAsync() + { + return await SendRequestAsync>("getFees"); + } + + /// + public RequestResult> GetFees() => GetFeesAsync().Result; + + /// + /// Gets a recent block hash. + /// + /// A task which may return a request result and recent block hash. + public async Task>> GetRecentBlockHashAsync() + { + return await SendRequestAsync>("getRecentBlockhash"); + } + + /// + public RequestResult> GetRecentBlockHash() + => GetRecentBlockHashAsync().Result; + + /// + /// Gets the maximum slot seen from retransmit stage. + /// + /// A task which may return a request result the maximum slot. + public async Task> GetMaxRetransmitSlotAsync() + { + return await SendRequestAsync("getMaxRetransmitSlot"); + } + + /// + public RequestResult GetMaxRetransmitSlot() + => GetMaxRetransmitSlotAsync().Result; + + /// + /// Gets the maximum slot seen from after shred insert. + /// + /// A task which may return a request result the maximum slot. + public async Task> GetMaxShredInsertSlotAsync() + { + return await SendRequestAsync("getMaxShredInsertSlot"); + } + + /// + public RequestResult GetMaxShredInsertSlot() + => GetMaxShredInsertSlotAsync().Result; + + /// + /// Gets the minimum balance required to make account rent exempt. + /// + /// The account data size. + /// A task which may return a request result and the rent exemption value. + public async Task> GetMinimumBalanceForRentExemptionAsync(long accountDataSize) + { + return await SendRequestAsync("getMinimumBalanceForRentExemption", + new List { accountDataSize }); + } + + /// + public RequestResult GetMinimumBalanceForRentExemption(long accountDataSize) + => GetMinimumBalanceForRentExemptionAsync(accountDataSize).Result; + + /// + /// Gets the genesis hash of the ledger. + /// + /// A task which may return a request result and a string representing the genesis hash. + public async Task> GetGenesisHashAsync() + { + return await SendRequestAsync("getGenesisHash"); + } + + /// + public RequestResult GetGenesisHash() + => GetGenesisHashAsync().Result; + + /// + /// Gets the identity pubkey for the current node. + /// + /// A task which may return a request result and a string representing the genesis hash. + public async Task> GetIdentityAsync() + { + return await SendRequestAsync("getIdentity"); + } + + /// + public RequestResult GetIdentity() + => GetIdentityAsync().Result; + + /// + /// Gets the current inflation governor. + /// + /// A task which may return a request result and an object representing the current inflation governor. + public async Task> GetInflationGovernorAsync() + { + return await SendRequestAsync("getInflationGovernor"); + } + + /// + public RequestResult GetInflationGovernor() + => GetInflationGovernorAsync().Result; + + /// + /// Gets the specific inflation values for the current epoch. + /// + /// A task which may return a request result and an object representing the current inflation rate. + public async Task> GetInflationRateAsync() + { + return await SendRequestAsync("getInflationRate"); + } + + /// + public RequestResult GetInflationRate() + => GetInflationRateAsync().Result; + + /// + /// Gets the inflation reward for a list of addresses for an epoch. + /// + /// The list of addresses to query for, as base-58 encoded strings. + /// The epoch. + /// A task which may return a request result and a list of objects representing the inflation reward. + public async Task>> GetInflationRewardAsync(List addresses, ulong epoch = 0) + { + if (epoch != 0) + return await SendRequestAsync>("getInflationReward", + new List { addresses, epoch }); + return await SendRequestAsync>("getInflationReward", new List { addresses }); + } + + /// + public RequestResult> GetInflationReward(List addresses, ulong epoch = 0) + => GetInflationRewardAsync(addresses, epoch).Result; + + /// + /// Gets the 20 largest accounts, by lamport balance. + /// + /// Filter results by account type. Available types: circulating/nonCirculating + /// A task which may return a request result the current slot. + /// Results may be cached up to two hours. + public async Task>>> GetLargestAccountsAsync(string filter) + { + return await SendRequestAsync>>("getLargestAccounts", + new List { new Dictionary { { "filter", filter } } }); + } + + /// + public RequestResult>> GetLargestAccounts(string filter) => + GetLargestAccountsAsync(filter).Result; + + /// + /// Gets the highest slot that the node has a snapshot for. + /// + /// A task which may return a request result the highest slot with a snapshot. + public async Task> GetSnapshotSlotAsync() + { + return await SendRequestAsync("getSnapshotSlot"); + } + + /// + public RequestResult GetSnapshotSlot() => GetSnapshotSlotAsync().Result; + + /// + /// Gets a list of recent performance samples. + /// + /// Unless searchTransactionHistory is included, this method only searches the recent status cache of signatures. + /// + /// + /// Maximum transaction signatures to return, between 1-720. Default is 720. + /// A task which may return a request result the signatures for the transactions. + public async Task>> GetRecentPerformanceSamplesAsync(ulong limit = 720) + { + return await SendRequestAsync>("getRecentPerformanceSamples", + new List { limit }); + } + + /// + public RequestResult> GetRecentPerformanceSamples(ulong limit = 720) + => GetRecentPerformanceSamplesAsync(limit).Result; + + /// + /// Gets confirmed signatures for transactions involving the address. + /// + /// Unless searchTransactionHistory is included, this method only searches the recent status cache of signatures. + /// + /// + /// The account address as base-58 encoded string. + /// Maximum transaction signatures to return, between 1-1000. Default is 1000. + /// Start searching backwards from this transaction signature. + /// Search until this transaction signature, if found before limit is reached. + /// A task which may return a request result the signatures for the transactions. + public async Task>> GetSignaturesForAddressAsync( + string accountPubKey, ulong limit = 1000, string before = "", string until = "") + { + var dictionary = new Dictionary { { "limit", limit } }; + + if (!string.IsNullOrWhiteSpace(before)) + dictionary.Add("before", before); + + if (!string.IsNullOrWhiteSpace(until)) + dictionary.Add("until", until); + + return await SendRequestAsync>("getSignaturesForAddress", + new List { accountPubKey, dictionary }); + } + + /// + public RequestResult> GetSignaturesForAddress( + string accountPubKey, ulong limit = 1000, string before = "", string until = "") + => GetSignaturesForAddressAsync(accountPubKey, limit, before, until).Result; + + /// + /// Gets the status of a list of signatures. + /// + /// Unless searchTransactionHistory is included, this method only searches the recent status cache of signatures. + /// + /// + /// The list of transactions to search status info for. + /// If the node should search for signatures in it's ledger cache. + /// A task which may return a request result the highest slot with a snapshot. + public async Task>>> GetSignatureStatusesAsync(List transactionHashes, bool searchTransactionHistory = false) + { + if (searchTransactionHistory) + return await SendRequestAsync>>("getSignatureStatuses", + new List { transactionHashes, new Dictionary { { "searchTransactionHistory", true } } }); + return await SendRequestAsync>>("getSignatureStatuses", + new List { transactionHashes }); + } + + /// + public RequestResult>> GetSignatureStatuses(List transactionHashes, + bool searchTransactionHistory = false) + => GetSignatureStatusesAsync(transactionHashes, searchTransactionHistory).Result; + + /// + /// Gets the current slot the node is processing + /// + /// A task which may return a request result the current slot. + public async Task> GetSlotAsync() + { + return await SendRequestAsync("getSlot"); + } + + /// + public RequestResult GetSlot() => GetSlotAsync().Result; + + /// + /// Gets the current slot leader. + /// + /// A task which may return a request result the slot leader. + public async Task> GetSlotLeaderAsync() + { + return await SendRequestAsync("getSlotLeader"); + } + + /// + public RequestResult GetSlotLeader() => GetSlotLeaderAsync().Result; + + /// + /// Gets the slot leaders for a given slot range. + /// + /// The start slot. + /// The result limit. + /// A task which may return a request result and a list slot leaders. + public async Task>> GetSlotLeadersAsync(ulong start, ulong limit) + { + return await SendRequestAsync>("getSlotLeaders", new List { start, limit }); + } + + /// + public RequestResult> GetSlotLeaders(ulong start, ulong limit) + => GetSlotLeadersAsync(start, limit).Result; + + #region Token Supply and Balances + + /// + /// Gets the epoch activation information for a stake account. + /// + /// Public key of account to query, as base-58 encoded string + /// Epoch for which to calculate activation details. + /// A task which may return a request result and information about the stake activation. + public async Task> GetStakeActivationAsync(string publicKey, ulong epoch = 0) + { + if (epoch != 0) + return await SendRequestAsync("getStakeActivation", + new List { publicKey, new Dictionary { { "epoch", epoch } } }); + return await SendRequestAsync("getStakeActivation", + new List { publicKey }); + } + + /// + public RequestResult GetStakeActivation(string publicKey, ulong epoch = 0) => + GetStakeActivationAsync(publicKey, epoch).Result; + + /// + /// Gets information about the current supply. + /// + /// A task which may return a request result and information about the supply. + public async Task>> GetSupplyAsync() + { + return await SendRequestAsync>("getSupply"); + } + + /// + public RequestResult> GetSupply() + => GetSupplyAsync().Result; + + /// + /// Gets the token balance of an SPL Token account. + /// + /// Public key of Token account to query, as base-58 encoded string. + /// A task which may return a request result and information about token account balance. + public async Task>> GetTokenAccountBalanceAsync( + string splTokenAccountPublicKey) + { + return await SendRequestAsync>("getTokenAccountBalance", + new List { splTokenAccountPublicKey }); + } + + /// + public RequestResult> GetTokenAccountBalance(string splTokenAccountPublicKey) + => GetTokenAccountBalanceAsync(splTokenAccountPublicKey).Result; + + /// + /// Gets all SPL Token accounts by approved delegate. + /// + /// Public key of account owner query, as base-58 encoded string. + /// Public key of the specific token Mint to limit accounts to, as base-58 encoded string. + /// Public key of the Token program ID that owns the accounts, as base-58 encoded string. + /// A task which may return a request result and information about token accounts by delegate. + public async Task>>> GetTokenAccountsByDelegateAsync( + string ownerPubKey, string tokenMintPubKey = "", string tokenProgramId = "") + { + if (string.IsNullOrWhiteSpace(tokenMintPubKey) && string.IsNullOrWhiteSpace(tokenProgramId)) + throw new ArgumentException("either tokenProgramId or tokenMintPubKey must be set"); + var options = new Dictionary(); + if (!string.IsNullOrWhiteSpace(tokenMintPubKey)) options.Add("mint", tokenMintPubKey); + if (!string.IsNullOrWhiteSpace(tokenProgramId)) options.Add("programId", tokenProgramId); + return await SendRequestAsync>>( + "getTokenAccountsByDelegate", + new List { ownerPubKey, options, new Dictionary { { "encoding", "jsonParsed" } } }); + } + + /// + public RequestResult>> GetTokenAccountsByDelegate( + string ownerPubKey, string tokenMintPubKey = "", string tokenProgramId = "") + => GetTokenAccountsByDelegateAsync(ownerPubKey, tokenMintPubKey, tokenProgramId).Result; + + /// + /// Gets all SPL Token accounts by token owner. + /// + /// Public key of account owner query, as base-58 encoded string. + /// Public key of the specific token Mint to limit accounts to, as base-58 encoded string. + /// Public key of the Token program ID that owns the accounts, as base-58 encoded string. + /// A task which may return a request result and information about token accounts by owner. + public async Task>>> GetTokenAccountsByOwnerAsync( + string ownerPubKey, string tokenMintPubKey = "", string tokenProgramId = "") + { + if (string.IsNullOrWhiteSpace(tokenMintPubKey) && string.IsNullOrWhiteSpace(tokenProgramId)) + throw new ArgumentException("either tokenProgramId or tokenMintPubKey must be set"); + var options = new Dictionary(); + if (!string.IsNullOrWhiteSpace(tokenMintPubKey)) options.Add("mint", tokenMintPubKey); + if (!string.IsNullOrWhiteSpace(tokenProgramId)) options.Add("programId", tokenProgramId); + return await SendRequestAsync>>( + "getTokenAccountsByOwner", + new List { ownerPubKey, options, new Dictionary { { "encoding", "jsonParsed" } } }); + } + + /// + public RequestResult>> GetTokenAccountsByOwner( + string ownerPubKey, string tokenMintPubKey = "", string tokenProgramId = "") + => GetTokenAccountsByOwnerAsync(ownerPubKey, tokenMintPubKey, tokenProgramId).Result; + + /// + /// Gets the 20 largest accounts of a particular SPL Token. + /// + /// Public key of Token Mint to query, as base-58 encoded string. + /// A task which may return a request result and information about largest accounts. + public async Task>>> GetTokenLargestAccountsAsync( + string tokenMintPubKey) + { + return await SendRequestAsync>>("getTokenLargestAccounts", + new List { tokenMintPubKey }); + } + + /// + public RequestResult>> GetTokenLargestAccounts(string tokenMintPubKey) + => GetTokenLargestAccountsAsync(tokenMintPubKey).Result; + + /// + /// Get the token supply of an SPL Token type. + /// + /// Public key of Token Mint to query, as base-58 encoded string. + /// A task which may return a request result and information about the supply. + public async Task>> GetTokenSupplyAsync(string tokenMintPubKey) + { + return await SendRequestAsync>("getTokenSupply", + new List { tokenMintPubKey }); + } + + /// + public RequestResult> GetTokenSupply(string tokenMintPubKey) + => GetTokenSupplyAsync(tokenMintPubKey).Result; + + #endregion + + /// + /// Gets the total transaction count of the ledger. + /// + /// A task which may return a request result and information about the transaction count. + public async Task> GetTransactionCountAsync() + { + return await SendRequestAsync("getTransactionCount"); + } + + /// + public RequestResult GetTransactionCount() + => GetTransactionCountAsync().Result; + + /// + /// Gets the current node's software version. + /// + /// A task which may return a request result and information about the node's software version. + public async Task> GetVersionAsync() + { + return await SendRequestAsync("getVersion"); + } + + /// + public RequestResult GetVersion() + => GetVersionAsync().Result; + + /// + /// Gets the account info and associated stake for all voting accounts in the current bank. + /// + /// A task which may return a request result and information about the vote accounts. + public async Task> GetVoteAccountsAsync() + { + return await SendRequestAsync("getVoteAccounts"); + } + + /// + public RequestResult GetVoteAccounts() + => GetVoteAccountsAsync().Result; + + /// + /// Gets the lowest slot that the node has information about in its ledger. + /// + /// This value may decrease over time if a node is configured to purging data. + /// + /// + /// A task which may return a request result and the minimum slot available. + public async Task> GetMinimumLedgerSlotAsync() + { + return await SendRequestAsync("minimumLedgerSlot"); + } + + /// + public RequestResult GetMinimumLedgerSlot() + => GetMinimumLedgerSlotAsync().Result; + + /// + /// Requests an airdrop to the passed pubKey of the passed lamports amount. + /// + /// The commitment parameter is optional, the default is used. + /// + /// + /// The public key of to receive the airdrop. + /// The amount of lamports to request. + /// The block commitment used to retrieve block hashes and verify success. + /// A task which may return a request result and the transaction signature of the airdrop, as base-58 encoded string.. + public async Task> RequestAirdropAsync(string pubKey, ulong lamports, + Commitment commitment = Commitment.Finalized) + { + return await SendRequestAsync("requestAirdrop", new List { pubKey, lamports, commitment }); + } + + /// + public RequestResult RequestAirdrop(string pubKey, ulong lamports, + Commitment commitment = Commitment.Finalized) + => RequestAirdropAsync(pubKey, lamports, commitment).Result; + + #region Transactions + + /// + /// Sends a transaction. + /// + /// The signed transaction as base-58 or base-64 encoded string. + /// The encoding of the transaction. + /// + /// A task which may return a request result and the first transaction signature embedded in the transaction, as base-58 encoded string. + /// + public async Task> SendTransactionAsync(string transaction, + BinaryEncoding encoding = BinaryEncoding.Base64) + { + return await SendRequestAsync("sendTransaction", + new List + { + transaction + }, + new Dictionary + { + { + "encoding", encoding + } + }); + } + + /// + public RequestResult SendTransaction(string transaction, + BinaryEncoding encoding = BinaryEncoding.Base64) + => SendTransactionAsync(transaction, encoding).Result; + + /// + /// Sends a transaction. + /// + /// The signed transaction as byte array. + /// + /// A task which may return a request result and the first transaction signature embedded in the transaction, as base-58 encoded string. + /// + public RequestResult SendTransaction(byte[] transaction) + => SendTransactionAsync(Convert.ToBase64String(transaction)).Result; + + /// + /// Simulate sending a transaction. + /// + /// The signed transaction as a byte array. + /// The encoding of the transaction. + /// + /// A task which may return a request result and the transaction status. + /// + public async Task>> SimulateTransactionAsync(string transaction, + BinaryEncoding encoding = BinaryEncoding.Base64) + { + return await SendRequestAsync>("simulateTransaction", + new List + { + transaction + }, new Dictionary + { + { + "encoding", encoding + } + }); + } + + /// + public RequestResult> SimulateTransaction(string transaction, + BinaryEncoding encoding = BinaryEncoding.Base64) + => SimulateTransactionAsync(transaction, encoding).Result; + + /// + /// Simulate sending a transaction. + /// + /// The signed transaction as a byte array. + /// + /// A task which may return a request result and the transaction status. + /// + public RequestResult> SimulateTransaction(byte[] transaction) + => SimulateTransactionAsync(Convert.ToBase64String(transaction)).Result; + + #endregion + } } \ No newline at end of file diff --git a/src/Solnet.Rpc/SolanaStreamingRpcClient.cs b/src/Solnet.Rpc/SolanaStreamingRpcClient.cs index 9db95972..4a360dd7 100644 --- a/src/Solnet.Rpc/SolanaStreamingRpcClient.cs +++ b/src/Solnet.Rpc/SolanaStreamingRpcClient.cs @@ -1,458 +1,458 @@ -using Microsoft.Extensions.Logging; -using Solnet.Rpc.Core; -using Solnet.Rpc.Core.Sockets; -using Solnet.Rpc.Messages; -using Solnet.Rpc.Models; -using Solnet.Rpc.Types; -using System; -using System.Collections.Generic; -using System.Net.WebSockets; -using System.Text; -using System.Text.Json; -using System.Text.Json.Serialization; -using System.Threading; -using System.Threading.Tasks; - -namespace Solnet.Rpc -{ - /// - /// Implementation of the Solana streaming RPC API abstraction client. - /// - public class SolanaStreamingRpcClient : StreamingRpcClient, IStreamingRpcClient - { - /// - /// Message Id generator. - /// - private readonly IdGenerator _idGenerator = new IdGenerator(); - - /// - /// Maps the internal ids to the unconfirmed subscription state objects. - /// - private readonly Dictionary unconfirmedRequests = new Dictionary(); - - /// - /// Maps the server ids to the confirmed subscription state objects. - /// - private readonly Dictionary confirmedSubscriptions = new Dictionary(); - - /// - /// Internal constructor. - /// - /// The url of the server to connect to. - /// The possible ILogger instance. - /// The possible IWebSocket instance. - internal SolanaStreamingRpcClient(string url, ILogger logger = null, IWebSocket websocket = default) : base(url, logger, websocket) - { - } - - /// - protected override void HandleNewMessage(Memory messagePayload) - { - Utf8JsonReader jsonReader = new Utf8JsonReader(messagePayload.Span); - jsonReader.Read(); - - if (_logger?.IsEnabled(LogLevel.Information) ?? false) - { - var str = Encoding.UTF8.GetString(messagePayload.Span); - _logger?.LogInformation($"[Received]{str}"); - } - - string prop = "", method = ""; - int id = -1, intResult = -1; - bool handled = false; - bool? boolResult = null; - - while (!handled && jsonReader.Read()) - { - switch (jsonReader.TokenType) - { - case JsonTokenType.PropertyName: - prop = jsonReader.GetString(); - if (prop == "params") - { - HandleDataMessage(ref jsonReader, method); - handled = true; - } - else if (prop == "error") - { - HandleError(ref jsonReader); - handled = true; - } - break; - case JsonTokenType.String: - if (prop == "method") - { - method = jsonReader.GetString(); - } - break; - case JsonTokenType.Number: - if (prop == "id") - { - id = jsonReader.GetInt32(); - } - else if (prop == "result") - { - intResult = jsonReader.GetInt32(); - } - if (id != -1 && intResult != -1) - { - ConfirmSubscription(id, intResult); - handled = true; - } - break; - case JsonTokenType.True: - case JsonTokenType.False: - if (prop == "result") - { - // this is the result of an unsubscription - // I don't think its supposed to ever be false if we correctly manage the subscription ids - // maybe future followup - boolResult = jsonReader.GetBoolean(); - } - break; - } - } - - if (boolResult.HasValue) - { - RemoveSubscription(id, boolResult.Value); - } - } - - /// - /// Handles and finishes parsing the contents of an error message. - /// - /// The jsonReader that read the message so far. - private void HandleError(ref Utf8JsonReader reader) - { - JsonSerializerOptions opts = new JsonSerializerOptions() { MaxDepth = 64, PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; - var err = JsonSerializer.Deserialize(ref reader, opts); - - reader.Read(); - - //var prop = reader.GetString(); //don't care about property name - - reader.Read(); - - var id = reader.GetInt32(); - - var sub = RemoveUnconfirmedSubscription(id); - - sub?.ChangeState(SubscriptionStatus.ErrorSubscribing, err.Message, err.Code.ToString()); - } - - - #region SubscriptionMapHandling - /// - /// Removes an unconfirmed subscription. - /// - /// The subscription id. - /// Returns the subscription object if it was found. - private SubscriptionState RemoveUnconfirmedSubscription(int id) - { - SubscriptionState sub; - lock (this) - { - if (!unconfirmedRequests.Remove(id, out sub)) - { - _logger.LogDebug(new EventId(), $"No unconfirmed subscription found with ID:{id}"); - } - } - return sub; - } - - /// - /// Removes a given subscription object from the map and notifies the object of the unsubscription. - /// - /// The subscription id. - /// Whether or not to notify that the subscription was removed. - private void RemoveSubscription(int id, bool shouldNotify) - { - SubscriptionState sub; - lock (this) - { - if (!confirmedSubscriptions.Remove(id, out sub)) - { - _logger.LogDebug(new EventId(), $"No subscription found with ID:{id}"); - } - } - if (shouldNotify) - { - sub?.ChangeState(SubscriptionStatus.Unsubscribed); - } - } - - /// - /// Confirms a given subcription based on the internal subscription id and the newly received external id. - /// Moves the subcription state object from the unconfirmed map to the confirmed map. - /// - /// - /// - private void ConfirmSubscription(int internalId, int resultId) - { - SubscriptionState sub; - lock (this) - { - if (unconfirmedRequests.Remove(internalId, out sub)) - { - sub.SubscriptionId = resultId; - confirmedSubscriptions.Add(resultId, sub); - } - } - - sub?.ChangeState(SubscriptionStatus.Subscribed); - } - - /// - /// Adds a new subscription state object into the unconfirmed subscriptions map. - /// - /// The subcription to add. - /// The internally generated id of the subscription. - private void AddSubscription(SubscriptionState subscription, int internalId) - { - lock (this) - { - unconfirmedRequests.Add(internalId, subscription); - } - } - - /// - /// Safely retrieves a subscription state object from a given subscription id. - /// - /// The subscription id. - /// The subscription state object. - private SubscriptionState RetrieveSubscription(int subscriptionId) - { - lock (this) - { - return confirmedSubscriptions[subscriptionId]; - } - } - #endregion - /// - /// Handles a notification message and finishes parsing the contents. - /// - /// The current JsonReader being used to parse the message. - /// The method parameter already parsed within the message. - private void HandleDataMessage(ref Utf8JsonReader reader, string method) - { - JsonSerializerOptions opts = new JsonSerializerOptions() { MaxDepth = 64, PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; - - switch (method) - { - case "accountNotification": - var accNotification = JsonSerializer.Deserialize>>(ref reader, opts); - if (accNotification == null) break; - NotifyData(accNotification.Subscription, accNotification.Result); - break; - case "logsNotification": - var logsNotification = JsonSerializer.Deserialize>>(ref reader, opts); - if (logsNotification == null) break; - NotifyData(logsNotification.Subscription, logsNotification.Result); - break; - case "programNotification": - var programNotification = JsonSerializer.Deserialize>>(ref reader, opts); - if (programNotification == null) break; - NotifyData(programNotification.Subscription, programNotification.Result); - break; - case "signatureNotification": - var signatureNotification = JsonSerializer.Deserialize>>(ref reader, opts); - if (signatureNotification == null) break; - NotifyData(signatureNotification.Subscription, signatureNotification.Result); - RemoveSubscription(signatureNotification.Subscription, true); - break; - case "slotNotification": - var slotNotification = JsonSerializer.Deserialize>(ref reader, opts); - if (slotNotification == null) break; - NotifyData(slotNotification.Subscription, slotNotification.Result); - break; - case "rootNotification": - var rootNotification = JsonSerializer.Deserialize>(ref reader, opts); - if (rootNotification == null) break; - NotifyData(rootNotification.Subscription, rootNotification.Result); - break; - } - } - - /// - /// Notifies a given subscription of a new data payload. - /// - /// The subscription ID received. - /// The parsed data payload to notify. - private void NotifyData(int subscription, object data) - { - var sub = RetrieveSubscription(subscription); - - sub.HandleData(data); - } - - #region AccountInfo - /// - public async Task SubscribeAccountInfoAsync(string pubkey, Action> callback) - - { - var sub = new SubscriptionState>(this, SubscriptionChannel.Account, callback, new List { pubkey }); - - var msg = new JsonRpcRequest(_idGenerator.GetNextId(), "accountSubscribe", new List { pubkey, new Dictionary { { "encoding", "base64" } } }); - - return await Subscribe(sub, msg).ConfigureAwait(false); - } - - /// - public SubscriptionState SubscribeAccountInfo(string pubkey, Action> callback) - => SubscribeAccountInfoAsync(pubkey, callback).Result; - #endregion - - #region Logs - /// - public async Task SubscribeLogInfoAsync(string pubkey, Action> callback) - { - var sub = new SubscriptionState>(this, SubscriptionChannel.Logs, callback, new List { pubkey }); - - var msg = new JsonRpcRequest(_idGenerator.GetNextId(), "logsSubscribe", new List { new Dictionary { { "mentions", new List { pubkey } } } }); - return await Subscribe(sub, msg).ConfigureAwait(false); - } - - /// - public SubscriptionState SubscribeLogInfo(string pubkey, Action> callback) - => SubscribeLogInfoAsync(pubkey, callback).Result; - - /// - public async Task SubscribeLogInfoAsync(LogsSubscriptionType subscriptionType, Action> callback) - { - var sub = new SubscriptionState>(this, SubscriptionChannel.Logs, callback, new List { subscriptionType }); - - - var msg = new JsonRpcRequest(_idGenerator.GetNextId(), "logsSubscribe", new List { subscriptionType }); - return await Subscribe(sub, msg).ConfigureAwait(false); - } - - /// - public SubscriptionState SubscribeLogInfo(LogsSubscriptionType subscriptionType, Action> callback) - => SubscribeLogInfoAsync(subscriptionType, callback).Result; - #endregion - - #region Signature - /// - public async Task SubscribeSignatureAsync(string transactionSignature, Action> callback) - { - var sub = new SubscriptionState>(this, SubscriptionChannel.Signature, callback, new List { transactionSignature }); - - var msg = new JsonRpcRequest(_idGenerator.GetNextId(), "signatureSubscribe", new List { transactionSignature }); - return await Subscribe(sub, msg).ConfigureAwait(false); - } - - /// - public SubscriptionState SubscribeSignature(string transactionSignature, Action> callback) - => SubscribeSignatureAsync(transactionSignature, callback).Result; - #endregion - - #region Program - /// - public async Task SubscribeProgramAsync(string transactionSignature, Action> callback) - { - var sub = new SubscriptionState>(this, SubscriptionChannel.Program, callback, - new List { transactionSignature, new Dictionary { { "encoding", "base64" } } }); - - var msg = new JsonRpcRequest(_idGenerator.GetNextId(), "programSubscribe", new List { transactionSignature, new Dictionary { { "encoding", "base64" } } }); - return await Subscribe(sub, msg).ConfigureAwait(false); - } - - /// - public SubscriptionState SubscribeProgram(string transactionSignature, Action> callback) - => SubscribeProgramAsync(transactionSignature, callback).Result; - #endregion - - #region SlotInfo - /// - public async Task SubscribeSlotInfoAsync(Action callback) - { - var sub = new SubscriptionState(this, SubscriptionChannel.Slot, callback); - - var msg = new JsonRpcRequest(_idGenerator.GetNextId(), "slotSubscribe", null); - return await Subscribe(sub, msg).ConfigureAwait(false); - } - - /// - public SubscriptionState SubscribeSlotInfo(Action callback) - => SubscribeSlotInfoAsync(callback).Result; - #endregion - - #region Root - /// - public async Task SubscribeRootAsync(Action callback) - { - var sub = new SubscriptionState(this, SubscriptionChannel.Root, callback); - - var msg = new JsonRpcRequest(_idGenerator.GetNextId(), "rootSubscribe", null); - return await Subscribe(sub, msg).ConfigureAwait(false); - } - - /// - public SubscriptionState SubscribeRoot(Action callback) - => SubscribeRootAsync(callback).Result; - #endregion - - /// - /// Internal subscribe function, finishes the serialization and sends the message payload. - /// - /// The subscription state object. - /// The message to be serialized and sent. - /// A task representing the state of the asynchronous operation- - private async Task Subscribe(SubscriptionState sub, JsonRpcRequest msg) - { - var json = JsonSerializer.SerializeToUtf8Bytes(msg, new JsonSerializerOptions - { - WriteIndented = false, - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - Converters = - { - new JsonStringEnumConverter(JsonNamingPolicy.CamelCase) - } - }); - - if (_logger?.IsEnabled(LogLevel.Information) ?? false) - { - var jsonString = Encoding.UTF8.GetString(json); - _logger?.LogInformation(new EventId(msg.Id, msg.Method), $"[Sending]{jsonString}"); - } - - ReadOnlyMemory mem = new ReadOnlyMemory(json); - - try - { - await ClientSocket.SendAsync(mem, WebSocketMessageType.Text, true, CancellationToken.None).ConfigureAwait(false); - AddSubscription(sub, msg.Id); - } - catch (Exception e) - { - sub.ChangeState(SubscriptionStatus.ErrorSubscribing, e.Message); - _logger?.LogDebug(new EventId(msg.Id, msg.Method), e, $"Unable to send message"); - } - - return sub; - } - - private string GetUnsubscribeMethodName(SubscriptionChannel channel) => channel switch - { - SubscriptionChannel.Account => "accountUnsubscribe", - SubscriptionChannel.Logs => "logsUnsubscribe", - SubscriptionChannel.Program => "programUnsubscribe", - SubscriptionChannel.Root => "rootUnsubscribe", - SubscriptionChannel.Signature => "signatureUnsubscribe", - SubscriptionChannel.Slot => "slotUnsubscribe", - _ => throw new ArgumentOutOfRangeException(nameof(channel), channel, "invalid message type") - }; - - /// - public async Task UnsubscribeAsync(SubscriptionState subscription) - { - var msg = new JsonRpcRequest(_idGenerator.GetNextId(), GetUnsubscribeMethodName(subscription.Channel), new List { subscription.SubscriptionId }); - - await Subscribe(subscription, msg).ConfigureAwait(false); - } - - /// - public void Unsubscribe(SubscriptionState subscription) => UnsubscribeAsync(subscription).Wait(); - } +using Microsoft.Extensions.Logging; +using Solnet.Rpc.Core; +using Solnet.Rpc.Core.Sockets; +using Solnet.Rpc.Messages; +using Solnet.Rpc.Models; +using Solnet.Rpc.Types; +using System; +using System.Collections.Generic; +using System.Net.WebSockets; +using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Threading; +using System.Threading.Tasks; + +namespace Solnet.Rpc +{ + /// + /// Implementation of the Solana streaming RPC API abstraction client. + /// + public class SolanaStreamingRpcClient : StreamingRpcClient, IStreamingRpcClient + { + /// + /// Message Id generator. + /// + private readonly IdGenerator _idGenerator = new IdGenerator(); + + /// + /// Maps the internal ids to the unconfirmed subscription state objects. + /// + private readonly Dictionary unconfirmedRequests = new Dictionary(); + + /// + /// Maps the server ids to the confirmed subscription state objects. + /// + private readonly Dictionary confirmedSubscriptions = new Dictionary(); + + /// + /// Internal constructor. + /// + /// The url of the server to connect to. + /// The possible ILogger instance. + /// The possible IWebSocket instance. + internal SolanaStreamingRpcClient(string url, ILogger logger = null, IWebSocket websocket = default) : base(url, logger, websocket) + { + } + + /// + protected override void HandleNewMessage(Memory messagePayload) + { + Utf8JsonReader jsonReader = new Utf8JsonReader(messagePayload.Span); + jsonReader.Read(); + + if (_logger?.IsEnabled(LogLevel.Information) ?? false) + { + var str = Encoding.UTF8.GetString(messagePayload.Span); + _logger?.LogInformation($"[Received]{str}"); + } + + string prop = "", method = ""; + int id = -1, intResult = -1; + bool handled = false; + bool? boolResult = null; + + while (!handled && jsonReader.Read()) + { + switch (jsonReader.TokenType) + { + case JsonTokenType.PropertyName: + prop = jsonReader.GetString(); + if (prop == "params") + { + HandleDataMessage(ref jsonReader, method); + handled = true; + } + else if (prop == "error") + { + HandleError(ref jsonReader); + handled = true; + } + break; + case JsonTokenType.String: + if (prop == "method") + { + method = jsonReader.GetString(); + } + break; + case JsonTokenType.Number: + if (prop == "id") + { + id = jsonReader.GetInt32(); + } + else if (prop == "result") + { + intResult = jsonReader.GetInt32(); + } + if (id != -1 && intResult != -1) + { + ConfirmSubscription(id, intResult); + handled = true; + } + break; + case JsonTokenType.True: + case JsonTokenType.False: + if (prop == "result") + { + // this is the result of an unsubscription + // I don't think its supposed to ever be false if we correctly manage the subscription ids + // maybe future followup + boolResult = jsonReader.GetBoolean(); + } + break; + } + } + + if (boolResult.HasValue) + { + RemoveSubscription(id, boolResult.Value); + } + } + + /// + /// Handles and finishes parsing the contents of an error message. + /// + /// The jsonReader that read the message so far. + private void HandleError(ref Utf8JsonReader reader) + { + JsonSerializerOptions opts = new JsonSerializerOptions() { MaxDepth = 64, PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; + var err = JsonSerializer.Deserialize(ref reader, opts); + + reader.Read(); + + //var prop = reader.GetString(); //don't care about property name + + reader.Read(); + + var id = reader.GetInt32(); + + var sub = RemoveUnconfirmedSubscription(id); + + sub?.ChangeState(SubscriptionStatus.ErrorSubscribing, err.Message, err.Code.ToString()); + } + + + #region SubscriptionMapHandling + /// + /// Removes an unconfirmed subscription. + /// + /// The subscription id. + /// Returns the subscription object if it was found. + private SubscriptionState RemoveUnconfirmedSubscription(int id) + { + SubscriptionState sub; + lock (this) + { + if (!unconfirmedRequests.Remove(id, out sub)) + { + _logger.LogDebug(new EventId(), $"No unconfirmed subscription found with ID:{id}"); + } + } + return sub; + } + + /// + /// Removes a given subscription object from the map and notifies the object of the unsubscription. + /// + /// The subscription id. + /// Whether or not to notify that the subscription was removed. + private void RemoveSubscription(int id, bool shouldNotify) + { + SubscriptionState sub; + lock (this) + { + if (!confirmedSubscriptions.Remove(id, out sub)) + { + _logger.LogDebug(new EventId(), $"No subscription found with ID:{id}"); + } + } + if (shouldNotify) + { + sub?.ChangeState(SubscriptionStatus.Unsubscribed); + } + } + + /// + /// Confirms a given subcription based on the internal subscription id and the newly received external id. + /// Moves the subcription state object from the unconfirmed map to the confirmed map. + /// + /// + /// + private void ConfirmSubscription(int internalId, int resultId) + { + SubscriptionState sub; + lock (this) + { + if (unconfirmedRequests.Remove(internalId, out sub)) + { + sub.SubscriptionId = resultId; + confirmedSubscriptions.Add(resultId, sub); + } + } + + sub?.ChangeState(SubscriptionStatus.Subscribed); + } + + /// + /// Adds a new subscription state object into the unconfirmed subscriptions map. + /// + /// The subcription to add. + /// The internally generated id of the subscription. + private void AddSubscription(SubscriptionState subscription, int internalId) + { + lock (this) + { + unconfirmedRequests.Add(internalId, subscription); + } + } + + /// + /// Safely retrieves a subscription state object from a given subscription id. + /// + /// The subscription id. + /// The subscription state object. + private SubscriptionState RetrieveSubscription(int subscriptionId) + { + lock (this) + { + return confirmedSubscriptions[subscriptionId]; + } + } + #endregion + /// + /// Handles a notification message and finishes parsing the contents. + /// + /// The current JsonReader being used to parse the message. + /// The method parameter already parsed within the message. + private void HandleDataMessage(ref Utf8JsonReader reader, string method) + { + JsonSerializerOptions opts = new JsonSerializerOptions() { MaxDepth = 64, PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; + + switch (method) + { + case "accountNotification": + var accNotification = JsonSerializer.Deserialize>>(ref reader, opts); + if (accNotification == null) break; + NotifyData(accNotification.Subscription, accNotification.Result); + break; + case "logsNotification": + var logsNotification = JsonSerializer.Deserialize>>(ref reader, opts); + if (logsNotification == null) break; + NotifyData(logsNotification.Subscription, logsNotification.Result); + break; + case "programNotification": + var programNotification = JsonSerializer.Deserialize>>(ref reader, opts); + if (programNotification == null) break; + NotifyData(programNotification.Subscription, programNotification.Result); + break; + case "signatureNotification": + var signatureNotification = JsonSerializer.Deserialize>>(ref reader, opts); + if (signatureNotification == null) break; + NotifyData(signatureNotification.Subscription, signatureNotification.Result); + RemoveSubscription(signatureNotification.Subscription, true); + break; + case "slotNotification": + var slotNotification = JsonSerializer.Deserialize>(ref reader, opts); + if (slotNotification == null) break; + NotifyData(slotNotification.Subscription, slotNotification.Result); + break; + case "rootNotification": + var rootNotification = JsonSerializer.Deserialize>(ref reader, opts); + if (rootNotification == null) break; + NotifyData(rootNotification.Subscription, rootNotification.Result); + break; + } + } + + /// + /// Notifies a given subscription of a new data payload. + /// + /// The subscription ID received. + /// The parsed data payload to notify. + private void NotifyData(int subscription, object data) + { + var sub = RetrieveSubscription(subscription); + + sub.HandleData(data); + } + + #region AccountInfo + /// + public async Task SubscribeAccountInfoAsync(string pubkey, Action> callback) + + { + var sub = new SubscriptionState>(this, SubscriptionChannel.Account, callback, new List { pubkey }); + + var msg = new JsonRpcRequest(_idGenerator.GetNextId(), "accountSubscribe", new List { pubkey, new Dictionary { { "encoding", "base64" } } }); + + return await Subscribe(sub, msg).ConfigureAwait(false); + } + + /// + public SubscriptionState SubscribeAccountInfo(string pubkey, Action> callback) + => SubscribeAccountInfoAsync(pubkey, callback).Result; + #endregion + + #region Logs + /// + public async Task SubscribeLogInfoAsync(string pubkey, Action> callback) + { + var sub = new SubscriptionState>(this, SubscriptionChannel.Logs, callback, new List { pubkey }); + + var msg = new JsonRpcRequest(_idGenerator.GetNextId(), "logsSubscribe", new List { new Dictionary { { "mentions", new List { pubkey } } } }); + return await Subscribe(sub, msg).ConfigureAwait(false); + } + + /// + public SubscriptionState SubscribeLogInfo(string pubkey, Action> callback) + => SubscribeLogInfoAsync(pubkey, callback).Result; + + /// + public async Task SubscribeLogInfoAsync(LogsSubscriptionType subscriptionType, Action> callback) + { + var sub = new SubscriptionState>(this, SubscriptionChannel.Logs, callback, new List { subscriptionType }); + + + var msg = new JsonRpcRequest(_idGenerator.GetNextId(), "logsSubscribe", new List { subscriptionType }); + return await Subscribe(sub, msg).ConfigureAwait(false); + } + + /// + public SubscriptionState SubscribeLogInfo(LogsSubscriptionType subscriptionType, Action> callback) + => SubscribeLogInfoAsync(subscriptionType, callback).Result; + #endregion + + #region Signature + /// + public async Task SubscribeSignatureAsync(string transactionSignature, Action> callback) + { + var sub = new SubscriptionState>(this, SubscriptionChannel.Signature, callback, new List { transactionSignature }); + + var msg = new JsonRpcRequest(_idGenerator.GetNextId(), "signatureSubscribe", new List { transactionSignature }); + return await Subscribe(sub, msg).ConfigureAwait(false); + } + + /// + public SubscriptionState SubscribeSignature(string transactionSignature, Action> callback) + => SubscribeSignatureAsync(transactionSignature, callback).Result; + #endregion + + #region Program + /// + public async Task SubscribeProgramAsync(string transactionSignature, Action> callback) + { + var sub = new SubscriptionState>(this, SubscriptionChannel.Program, callback, + new List { transactionSignature, new Dictionary { { "encoding", "base64" } } }); + + var msg = new JsonRpcRequest(_idGenerator.GetNextId(), "programSubscribe", new List { transactionSignature, new Dictionary { { "encoding", "base64" } } }); + return await Subscribe(sub, msg).ConfigureAwait(false); + } + + /// + public SubscriptionState SubscribeProgram(string transactionSignature, Action> callback) + => SubscribeProgramAsync(transactionSignature, callback).Result; + #endregion + + #region SlotInfo + /// + public async Task SubscribeSlotInfoAsync(Action callback) + { + var sub = new SubscriptionState(this, SubscriptionChannel.Slot, callback); + + var msg = new JsonRpcRequest(_idGenerator.GetNextId(), "slotSubscribe", null); + return await Subscribe(sub, msg).ConfigureAwait(false); + } + + /// + public SubscriptionState SubscribeSlotInfo(Action callback) + => SubscribeSlotInfoAsync(callback).Result; + #endregion + + #region Root + /// + public async Task SubscribeRootAsync(Action callback) + { + var sub = new SubscriptionState(this, SubscriptionChannel.Root, callback); + + var msg = new JsonRpcRequest(_idGenerator.GetNextId(), "rootSubscribe", null); + return await Subscribe(sub, msg).ConfigureAwait(false); + } + + /// + public SubscriptionState SubscribeRoot(Action callback) + => SubscribeRootAsync(callback).Result; + #endregion + + /// + /// Internal subscribe function, finishes the serialization and sends the message payload. + /// + /// The subscription state object. + /// The message to be serialized and sent. + /// A task representing the state of the asynchronous operation- + private async Task Subscribe(SubscriptionState sub, JsonRpcRequest msg) + { + var json = JsonSerializer.SerializeToUtf8Bytes(msg, new JsonSerializerOptions + { + WriteIndented = false, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + Converters = + { + new JsonStringEnumConverter(JsonNamingPolicy.CamelCase) + } + }); + + if (_logger?.IsEnabled(LogLevel.Information) ?? false) + { + var jsonString = Encoding.UTF8.GetString(json); + _logger?.LogInformation(new EventId(msg.Id, msg.Method), $"[Sending]{jsonString}"); + } + + ReadOnlyMemory mem = new ReadOnlyMemory(json); + + try + { + await ClientSocket.SendAsync(mem, WebSocketMessageType.Text, true, CancellationToken.None).ConfigureAwait(false); + AddSubscription(sub, msg.Id); + } + catch (Exception e) + { + sub.ChangeState(SubscriptionStatus.ErrorSubscribing, e.Message); + _logger?.LogDebug(new EventId(msg.Id, msg.Method), e, $"Unable to send message"); + } + + return sub; + } + + private string GetUnsubscribeMethodName(SubscriptionChannel channel) => channel switch + { + SubscriptionChannel.Account => "accountUnsubscribe", + SubscriptionChannel.Logs => "logsUnsubscribe", + SubscriptionChannel.Program => "programUnsubscribe", + SubscriptionChannel.Root => "rootUnsubscribe", + SubscriptionChannel.Signature => "signatureUnsubscribe", + SubscriptionChannel.Slot => "slotUnsubscribe", + _ => throw new ArgumentOutOfRangeException(nameof(channel), channel, "invalid message type") + }; + + /// + public async Task UnsubscribeAsync(SubscriptionState subscription) + { + var msg = new JsonRpcRequest(_idGenerator.GetNextId(), GetUnsubscribeMethodName(subscription.Channel), new List { subscription.SubscriptionId }); + + await Subscribe(subscription, msg).ConfigureAwait(false); + } + + /// + public void Unsubscribe(SubscriptionState subscription) => UnsubscribeAsync(subscription).Wait(); + } } \ No newline at end of file diff --git a/src/Solnet.Rpc/Types/BinaryEncoding.cs b/src/Solnet.Rpc/Types/BinaryEncoding.cs index 55153b74..2cead648 100644 --- a/src/Solnet.Rpc/Types/BinaryEncoding.cs +++ b/src/Solnet.Rpc/Types/BinaryEncoding.cs @@ -1,21 +1,21 @@ -namespace Solnet.Rpc.Types -{ - /// - /// The encodings used for binary data to interact with the Solana nodes. - /// - public enum BinaryEncoding - { - /// - /// Request json parsed data, when a parser is available. - /// - JsonParsed, - /// - /// Base58 encoding. - /// - Base58, - /// - /// Base64 encoding. - /// - Base64 - } +namespace Solnet.Rpc.Types +{ + /// + /// The encodings used for binary data to interact with the Solana nodes. + /// + public enum BinaryEncoding + { + /// + /// Request json parsed data, when a parser is available. + /// + JsonParsed, + /// + /// Base58 encoding. + /// + Base58, + /// + /// Base64 encoding. + /// + Base64 + } } \ No newline at end of file diff --git a/src/Solnet.Rpc/Types/Commitment.cs b/src/Solnet.Rpc/Types/Commitment.cs index 5923a0de..7f635bc2 100644 --- a/src/Solnet.Rpc/Types/Commitment.cs +++ b/src/Solnet.Rpc/Types/Commitment.cs @@ -1,9 +1,9 @@ -namespace Solnet.Rpc.Types -{ - public enum Commitment - { - Finalized, - Confirmed, - Processed - } +namespace Solnet.Rpc.Types +{ + public enum Commitment + { + Finalized, + Confirmed, + Processed + } } \ No newline at end of file diff --git a/src/Solnet.Rpc/Types/LogsSubscriptionType.cs b/src/Solnet.Rpc/Types/LogsSubscriptionType.cs index 9694e3fd..b57c4635 100644 --- a/src/Solnet.Rpc/Types/LogsSubscriptionType.cs +++ b/src/Solnet.Rpc/Types/LogsSubscriptionType.cs @@ -1,8 +1,8 @@ -namespace Solnet.Rpc.Types -{ - public enum LogsSubscriptionType - { - All, - AllWithVotes - } +namespace Solnet.Rpc.Types +{ + public enum LogsSubscriptionType + { + All, + AllWithVotes + } } \ No newline at end of file diff --git a/src/Solnet.Rpc/Types/TransactionDetails.cs b/src/Solnet.Rpc/Types/TransactionDetails.cs index 73dd7d70..a9204860 100644 --- a/src/Solnet.Rpc/Types/TransactionDetails.cs +++ b/src/Solnet.Rpc/Types/TransactionDetails.cs @@ -1,21 +1,21 @@ -namespace Solnet.Rpc.Types -{ - /// - /// Used to specify which block data to retrieve. - /// - public enum TransactionDetails - { - /// - /// Retrieve the full block data. - /// - Full, - /// - /// Retrieve only signatures, leaving out detailed transaction data. - /// - Signatures, - /// - /// Retrieve only basic block data. - /// - None - } +namespace Solnet.Rpc.Types +{ + /// + /// Used to specify which block data to retrieve. + /// + public enum TransactionDetails + { + /// + /// Retrieve the full block data. + /// + Full, + /// + /// Retrieve only signatures, leaving out detailed transaction data. + /// + Signatures, + /// + /// Retrieve only basic block data. + /// + None + } } \ No newline at end of file diff --git a/src/Solnet.Rpc/Utilities/AddressExtensions.cs b/src/Solnet.Rpc/Utilities/AddressExtensions.cs index 3f4b54ea..cf1de605 100644 --- a/src/Solnet.Rpc/Utilities/AddressExtensions.cs +++ b/src/Solnet.Rpc/Utilities/AddressExtensions.cs @@ -1,84 +1,84 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Security.Cryptography; -using System.Text; - -namespace Solnet.Rpc.Utilities -{ - public static class AddressExtensions - { - /// - /// The bytes of the `ProgramDerivedAddress` string. - /// - private static readonly byte[] ProgramDerivedAddressBytes = Encoding.UTF8.GetBytes("ProgramDerivedAddress"); - - /// - /// Derives a program address. - /// - /// The address seeds. - /// The program Id. - /// The address derived. - /// Throws exception when one of the seeds has an invalid length. - /// Throws exception when the resulting address doesn't fall off the Ed25519 curve. - public static byte[] CreateProgramAddress(IList seeds, byte[] programId) - { - var buffer = new MemoryStream(32 * seeds.Count + ProgramDerivedAddressBytes.Length + programId.Length); - - foreach (var seed in seeds) - { - if (seed.Length > 32) - { - throw new ArgumentException("max seed length exceeded", nameof(seeds)); - } - - buffer.Write(seed); - } - - buffer.Write(programId); - buffer.Write(ProgramDerivedAddressBytes); - - var hash = SHA256.HashData(buffer.ToArray()); - - if (hash.IsOnCurve()) - { - throw new Exception("invalid seeds, address must fall off curve"); - } - - return hash; - } - - /// - /// Attempts to find a program address for the passed seeds and program Id. - /// - /// The address seeds. - /// The program Id. - /// A tuple corresponding to the address and nonce found. - /// Throws exception when it is unable to find a viable nonce for the address. - public static (byte[] Address, int Nonce) FindProgramAddress(IEnumerable seeds, byte[] programId) - { - var nonce = 255; - var buffer = seeds.ToList(); - - while (nonce-- != 0) - { - byte[] address; - try - { - buffer.Add(new[] { (byte)nonce }); - address = CreateProgramAddress(buffer, programId); - } - catch (Exception) - { - buffer.RemoveAt(buffer.Count - 1); - continue; - } - - return (address, nonce); - } - - throw new Exception("unable to find viable program address nonce"); - } - } +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Text; + +namespace Solnet.Rpc.Utilities +{ + public static class AddressExtensions + { + /// + /// The bytes of the `ProgramDerivedAddress` string. + /// + private static readonly byte[] ProgramDerivedAddressBytes = Encoding.UTF8.GetBytes("ProgramDerivedAddress"); + + /// + /// Derives a program address. + /// + /// The address seeds. + /// The program Id. + /// The address derived. + /// Throws exception when one of the seeds has an invalid length. + /// Throws exception when the resulting address doesn't fall off the Ed25519 curve. + public static byte[] CreateProgramAddress(IList seeds, byte[] programId) + { + var buffer = new MemoryStream(32 * seeds.Count + ProgramDerivedAddressBytes.Length + programId.Length); + + foreach (var seed in seeds) + { + if (seed.Length > 32) + { + throw new ArgumentException("max seed length exceeded", nameof(seeds)); + } + + buffer.Write(seed); + } + + buffer.Write(programId); + buffer.Write(ProgramDerivedAddressBytes); + + var hash = SHA256.HashData(buffer.ToArray()); + + if (hash.IsOnCurve()) + { + throw new Exception("invalid seeds, address must fall off curve"); + } + + return hash; + } + + /// + /// Attempts to find a program address for the passed seeds and program Id. + /// + /// The address seeds. + /// The program Id. + /// A tuple corresponding to the address and nonce found. + /// Throws exception when it is unable to find a viable nonce for the address. + public static (byte[] Address, int Nonce) FindProgramAddress(IEnumerable seeds, byte[] programId) + { + var nonce = 255; + var buffer = seeds.ToList(); + + while (nonce-- != 0) + { + byte[] address; + try + { + buffer.Add(new[] { (byte)nonce }); + address = CreateProgramAddress(buffer, programId); + } + catch (Exception) + { + buffer.RemoveAt(buffer.Count - 1); + continue; + } + + return (address, nonce); + } + + throw new Exception("unable to find viable program address nonce"); + } + } } \ No newline at end of file diff --git a/src/Solnet.Rpc/Utilities/Ed25519Extensions.cs b/src/Solnet.Rpc/Utilities/Ed25519Extensions.cs index af8d885c..4e46424c 100644 --- a/src/Solnet.Rpc/Utilities/Ed25519Extensions.cs +++ b/src/Solnet.Rpc/Utilities/Ed25519Extensions.cs @@ -1,97 +1,97 @@ -using System.Numerics; - -namespace Solnet.Rpc.Utilities -{ - /* Ported and refactored from Java to C# by Hans Wolff, 10/10/2013 - * Released to the public domain - * / - /* Java code written by k3d3 - * Source: https://github.com/k3d3/ed25519-java/blob/master/ed25519.java - * Released to the public domain - */ - - public static class Ed25519Extensions - { - private static BigInteger ExpMod(BigInteger number, BigInteger exponent, BigInteger modulo) - { - if (exponent.Equals(BigInteger.Zero)) - { - return BigInteger.One; - } - BigInteger t = BigInteger.Pow(ExpMod(number, exponent / Two, modulo), 2).Mod(modulo); - if (!exponent.IsEven) - { - t *= number; - t = t.Mod(modulo); - } - return t; - } - - private static BigInteger Inv(BigInteger x) - { - return ExpMod(x, Qm2, Q); - } - - private static BigInteger RecoverX(BigInteger y) - { - BigInteger y2 = y * y; - BigInteger xx = (y2 - 1) * Inv(D * y2 + 1); - BigInteger x = ExpMod(xx, Qp3 / Eight, Q); - if (!(x * x - xx).Mod(Q).Equals(BigInteger.Zero)) - { - x = (x * I).Mod(Q); - } - if (!x.IsEven) - { - x = Q - x; - } - return x; - } - - private static bool IsOnCurve(BigInteger x, BigInteger y) - { - BigInteger xx = x * x; - BigInteger yy = y * y; - BigInteger dxxyy = D * yy * xx; - return (yy - xx - dxxyy - 1).Mod(Q).Equals(BigInteger.Zero); - } - - public static bool IsOnCurve(this byte[] key) - { - BigInteger y = new BigInteger(key) & Un; - BigInteger x = RecoverX(y); - - return IsOnCurve(x, y); - } - - private static readonly BigInteger Q = - BigInteger.Parse("57896044618658097711785492504343953926634992332820282019728792003956564819949"); - - private static readonly BigInteger Qm2 = - BigInteger.Parse("57896044618658097711785492504343953926634992332820282019728792003956564819947"); - - private static readonly BigInteger Qp3 = - BigInteger.Parse("57896044618658097711785492504343953926634992332820282019728792003956564819952"); - - private static readonly BigInteger D = - BigInteger.Parse("-4513249062541557337682894930092624173785641285191125241628941591882900924598840740"); - - private static readonly BigInteger I = - BigInteger.Parse("19681161376707505956807079304988542015446066515923890162744021073123829784752"); - - private static readonly BigInteger Un = - BigInteger.Parse("57896044618658097711785492504343953926634992332820282019728792003956564819967"); - - private static readonly BigInteger Two = new(2); - private static readonly BigInteger Eight = new(8); - } - - internal static class BigIntegerHelpers - { - public static BigInteger Mod(this BigInteger num, BigInteger modulo) - { - var result = num % modulo; - return result < 0 ? result + modulo : result; - } - } +using System.Numerics; + +namespace Solnet.Rpc.Utilities +{ + /* Ported and refactored from Java to C# by Hans Wolff, 10/10/2013 + * Released to the public domain + * / + /* Java code written by k3d3 + * Source: https://github.com/k3d3/ed25519-java/blob/master/ed25519.java + * Released to the public domain + */ + + public static class Ed25519Extensions + { + private static BigInteger ExpMod(BigInteger number, BigInteger exponent, BigInteger modulo) + { + if (exponent.Equals(BigInteger.Zero)) + { + return BigInteger.One; + } + BigInteger t = BigInteger.Pow(ExpMod(number, exponent / Two, modulo), 2).Mod(modulo); + if (!exponent.IsEven) + { + t *= number; + t = t.Mod(modulo); + } + return t; + } + + private static BigInteger Inv(BigInteger x) + { + return ExpMod(x, Qm2, Q); + } + + private static BigInteger RecoverX(BigInteger y) + { + BigInteger y2 = y * y; + BigInteger xx = (y2 - 1) * Inv(D * y2 + 1); + BigInteger x = ExpMod(xx, Qp3 / Eight, Q); + if (!(x * x - xx).Mod(Q).Equals(BigInteger.Zero)) + { + x = (x * I).Mod(Q); + } + if (!x.IsEven) + { + x = Q - x; + } + return x; + } + + private static bool IsOnCurve(BigInteger x, BigInteger y) + { + BigInteger xx = x * x; + BigInteger yy = y * y; + BigInteger dxxyy = D * yy * xx; + return (yy - xx - dxxyy - 1).Mod(Q).Equals(BigInteger.Zero); + } + + public static bool IsOnCurve(this byte[] key) + { + BigInteger y = new BigInteger(key) & Un; + BigInteger x = RecoverX(y); + + return IsOnCurve(x, y); + } + + private static readonly BigInteger Q = + BigInteger.Parse("57896044618658097711785492504343953926634992332820282019728792003956564819949"); + + private static readonly BigInteger Qm2 = + BigInteger.Parse("57896044618658097711785492504343953926634992332820282019728792003956564819947"); + + private static readonly BigInteger Qp3 = + BigInteger.Parse("57896044618658097711785492504343953926634992332820282019728792003956564819952"); + + private static readonly BigInteger D = + BigInteger.Parse("-4513249062541557337682894930092624173785641285191125241628941591882900924598840740"); + + private static readonly BigInteger I = + BigInteger.Parse("19681161376707505956807079304988542015446066515923890162744021073123829784752"); + + private static readonly BigInteger Un = + BigInteger.Parse("57896044618658097711785492504343953926634992332820282019728792003956564819967"); + + private static readonly BigInteger Two = new(2); + private static readonly BigInteger Eight = new(8); + } + + internal static class BigIntegerHelpers + { + public static BigInteger Mod(this BigInteger num, BigInteger modulo) + { + var result = num % modulo; + return result < 0 ? result + modulo : result; + } + } } \ No newline at end of file diff --git a/src/Solnet.Rpc/Utilities/ShortVectorEncoding.cs b/src/Solnet.Rpc/Utilities/ShortVectorEncoding.cs index e79d316b..65907609 100644 --- a/src/Solnet.Rpc/Utilities/ShortVectorEncoding.cs +++ b/src/Solnet.Rpc/Utilities/ShortVectorEncoding.cs @@ -1,34 +1,34 @@ -using System; - -namespace Solnet.Rpc.Utilities -{ - - public static class ShortVectorEncoding - { - public static byte[] EncodeLength(int len) - { - var output = new byte[10]; - var remLen = len; - var cursor = 0; - - for (; ; ) - { - var elem = remLen & 0x7f; - remLen >>= 7; - if (remLen == 0) - { - output[cursor] = (byte)elem; - break; - } - elem |= 0x80; - output[cursor] = (byte)elem; - cursor += 1; - } - - var bytes = new byte[cursor + 1]; - Array.Copy(output, 0, bytes, 0, cursor + 1); - - return bytes; - } - } +using System; + +namespace Solnet.Rpc.Utilities +{ + + public static class ShortVectorEncoding + { + public static byte[] EncodeLength(int len) + { + var output = new byte[10]; + var remLen = len; + var cursor = 0; + + for (; ; ) + { + var elem = remLen & 0x7f; + remLen >>= 7; + if (remLen == 0) + { + output[cursor] = (byte)elem; + break; + } + elem |= 0x80; + output[cursor] = (byte)elem; + cursor += 1; + } + + var bytes = new byte[cursor + 1]; + Array.Copy(output, 0, bytes, 0, cursor + 1); + + return bytes; + } + } } \ No newline at end of file diff --git a/src/Solnet.Wallet/Account.cs b/src/Solnet.Wallet/Account.cs index c1bc04ad..2d5cbd6d 100644 --- a/src/Solnet.Wallet/Account.cs +++ b/src/Solnet.Wallet/Account.cs @@ -1,116 +1,116 @@ -using Chaos.NaCl; -using NBitcoin.DataEncoders; -using System; -using System.Security.Cryptography; - -namespace Solnet.Wallet -{ - public class Account - { - /// - /// The base58 encoder instance. - /// - private static readonly Base58Encoder Encoder = new(); - - /// - /// Private key length. - /// - private const int PrivateKeyLength = 64; - - /// - /// Public key length. - /// - private const int PublicKeyLength = 32; - - /// - /// The private key. - /// - private readonly byte[] _privateKey; - - /// - /// The public key. - /// - private readonly byte[] _publicKey; - - /// - /// Initialize an account. Generating a random seed for the Ed25519 key pair. - /// - public Account() - { - var seed = GenerateRandomSeed(); - - (_privateKey, _publicKey) = Ed25519Extensions.EdKeyPairFromSeed(seed); - } - - /// - /// Initialize an account with the passed private and public keys. - /// - /// The private key. - /// The public key. - public Account(byte[] privateKey, byte[] publicKey) - { - if (privateKey.Length != PrivateKeyLength) - throw new ArgumentException("invalid key length", nameof(privateKey)); - if (publicKey.Length != PublicKeyLength) - throw new ArgumentException("invalid key length", nameof(privateKey)); - - _privateKey = privateKey; - _publicKey = publicKey; - } - - /// - /// Get the private key encoded as base58. - /// - public string GetPrivateKey => Encoder.EncodeData(_privateKey); - - /// - /// Get the public key encoded as base58. - /// - public string GetPublicKey => Encoder.EncodeData(_publicKey); - - /// - /// Get the public key as a byte array. - /// - public byte[] PublicKey => _publicKey; - - /// - /// Get the private key as a byte array. - /// - public byte[] PrivateKey => _privateKey; - - - /// - /// Verify the signed message. - /// - /// The signed message. - /// The signature of the message. - /// - public bool Verify(byte[] message, byte[] signature) - { - return Ed25519.Verify(signature, message, _publicKey); - } - - /// - /// Sign the data. - /// - /// The data to sign. - /// The signature of the data. - public byte[] Sign(byte[] message) - { - var signature = new byte[64]; - Ed25519.Sign(signature, message, _privateKey); - return signature; - } - - /// - /// Generates a random seed for the Ed25519 key pair. - /// - /// The seed as byte array. - private byte[] GenerateRandomSeed() - { - var bytes = new byte[Ed25519.PrivateKeySeedSizeInBytes]; - RandomNumberGenerator.Create().GetBytes(bytes); - return bytes; - } - } +using Chaos.NaCl; +using NBitcoin.DataEncoders; +using System; +using System.Security.Cryptography; + +namespace Solnet.Wallet +{ + public class Account + { + /// + /// The base58 encoder instance. + /// + private static readonly Base58Encoder Encoder = new(); + + /// + /// Private key length. + /// + private const int PrivateKeyLength = 64; + + /// + /// Public key length. + /// + private const int PublicKeyLength = 32; + + /// + /// The private key. + /// + private readonly byte[] _privateKey; + + /// + /// The public key. + /// + private readonly byte[] _publicKey; + + /// + /// Initialize an account. Generating a random seed for the Ed25519 key pair. + /// + public Account() + { + var seed = GenerateRandomSeed(); + + (_privateKey, _publicKey) = Ed25519Extensions.EdKeyPairFromSeed(seed); + } + + /// + /// Initialize an account with the passed private and public keys. + /// + /// The private key. + /// The public key. + public Account(byte[] privateKey, byte[] publicKey) + { + if (privateKey.Length != PrivateKeyLength) + throw new ArgumentException("invalid key length", nameof(privateKey)); + if (publicKey.Length != PublicKeyLength) + throw new ArgumentException("invalid key length", nameof(privateKey)); + + _privateKey = privateKey; + _publicKey = publicKey; + } + + /// + /// Get the private key encoded as base58. + /// + public string GetPrivateKey => Encoder.EncodeData(_privateKey); + + /// + /// Get the public key encoded as base58. + /// + public string GetPublicKey => Encoder.EncodeData(_publicKey); + + /// + /// Get the public key as a byte array. + /// + public byte[] PublicKey => _publicKey; + + /// + /// Get the private key as a byte array. + /// + public byte[] PrivateKey => _privateKey; + + + /// + /// Verify the signed message. + /// + /// The signed message. + /// The signature of the message. + /// + public bool Verify(byte[] message, byte[] signature) + { + return Ed25519.Verify(signature, message, _publicKey); + } + + /// + /// Sign the data. + /// + /// The data to sign. + /// The signature of the data. + public byte[] Sign(byte[] message) + { + var signature = new byte[64]; + Ed25519.Sign(signature, message, _privateKey); + return signature; + } + + /// + /// Generates a random seed for the Ed25519 key pair. + /// + /// The seed as byte array. + private byte[] GenerateRandomSeed() + { + var bytes = new byte[Ed25519.PrivateKeySeedSizeInBytes]; + RandomNumberGenerator.Create().GetBytes(bytes); + return bytes; + } + } } \ No newline at end of file diff --git a/src/Solnet.Wallet/Ed25519Bip32.cs b/src/Solnet.Wallet/Ed25519Bip32.cs index 465391c1..76ca0d0f 100644 --- a/src/Solnet.Wallet/Ed25519Bip32.cs +++ b/src/Solnet.Wallet/Ed25519Bip32.cs @@ -1,134 +1,134 @@ -using Solnet.Wallet.Utilities; -using System; -using System.Linq; -using System.Security.Cryptography; -using System.Text; -using System.Text.RegularExpressions; - -namespace Solnet.Wallet -{ - /// - /// An implementation of Ed25519 based BIP32 key generation. - /// - public class Ed25519Bip32 - { - /// - /// The seed for the Ed25519 BIP32 HMAC-SHA512 master key calculation. - /// - private const string Curve = "ed25519 seed"; - - /// - /// - /// - private const uint HardenedOffset = 0x80000000; - - /// - /// The computed master key. - /// - private readonly byte[] _masterKey; - - /// - /// The computed chain code. - /// - private readonly byte[] _chainCode; - - /// - /// Initialize the ed25519 based bip32 key generator with the passed seed. - /// - /// The seed. - public Ed25519Bip32(byte[] seed) - { - (_masterKey, _chainCode) = GetMasterKeyFromSeed(seed); - } - - /// - /// Gets the master key used for key generation from the passed seed. - /// - /// The seed used to calculate the master key. - /// A tuple consisting of the key and corresponding chain code. - private (byte[] Key, byte[] ChainCode) GetMasterKeyFromSeed(byte[] seed) - => HmacSha512(Encoding.UTF8.GetBytes(Curve), seed); - - /// - /// Computes the child key. - /// - /// The key used to derive from. - /// The chain code for derivation. - /// The index of the key to the derive. - /// A tuple consisting of the key and corresponding chain code. - private (byte[] Key, byte[] ChainCode) GetChildKeyDerivation(byte[] key, byte[] chainCode, uint index) - { - BigEndianBuffer buffer = new BigEndianBuffer(); - - buffer.Write(new byte[] { 0 }); - buffer.Write(key); - buffer.WriteUInt(index); - - return HmacSha512(chainCode, buffer.ToArray()); - } - - /// - /// Computes the HMAC SHA 512 of the byte array passed into data. - /// - /// The byte array to be used as the HMAC SHA512 key. - /// The data to calculate the HMAC SHA512 on. - /// A tuple consisting of the key and corresponding chain code. - private static (byte[] Key, byte[] ChainCode) HmacSha512(byte[] keyBuffer, byte[] data) - { - using var hmacSha512 = new HMACSHA512(keyBuffer); - var i = hmacSha512.ComputeHash(data); - - var il = i.Slice(0, 32); - var ir = i.Slice(32); - - return (Key: il, ChainCode: ir); - } - - /// - /// Checks if the derivation path is valid. - /// Returns true if the path is valid, otherwise false. - /// - /// The derivation path. - /// A boolean. - private static bool IsValidPath(string path) - { - var regex = new Regex("^m(\\/[0-9]+')+$"); - - if (!regex.IsMatch(path)) - return false; - - var valid = !(path.Split('/') - .Slice(1) - .Select(a => a.Replace("'", "")) - .Any(a => !int.TryParse(a, out _))); - - return valid; - } - - /// - /// Derives a child key from the passed derivation path. - /// - /// The derivation path. - /// The key and chaincode. - /// Thrown when the passed derivation path is invalid. - public (byte[] Key, byte[] ChainCode) DerivePath(string path) - { - if (!IsValidPath(path)) - throw new FormatException("Invalid derivation path"); - - var segments = path - .Split('/') - .Slice(1) - .Select(a => a.Replace("'", "")) - .Select(a => Convert.ToUInt32(a, 10)); - - var results = segments - .Aggregate( - (_masterKey, _chainCode), - (masterKeyFromSeed, next) => - GetChildKeyDerivation(masterKeyFromSeed._masterKey, masterKeyFromSeed._chainCode, next + HardenedOffset)); - - return results; - } - } +using Solnet.Wallet.Utilities; +using System; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Text.RegularExpressions; + +namespace Solnet.Wallet +{ + /// + /// An implementation of Ed25519 based BIP32 key generation. + /// + public class Ed25519Bip32 + { + /// + /// The seed for the Ed25519 BIP32 HMAC-SHA512 master key calculation. + /// + private const string Curve = "ed25519 seed"; + + /// + /// + /// + private const uint HardenedOffset = 0x80000000; + + /// + /// The computed master key. + /// + private readonly byte[] _masterKey; + + /// + /// The computed chain code. + /// + private readonly byte[] _chainCode; + + /// + /// Initialize the ed25519 based bip32 key generator with the passed seed. + /// + /// The seed. + public Ed25519Bip32(byte[] seed) + { + (_masterKey, _chainCode) = GetMasterKeyFromSeed(seed); + } + + /// + /// Gets the master key used for key generation from the passed seed. + /// + /// The seed used to calculate the master key. + /// A tuple consisting of the key and corresponding chain code. + private (byte[] Key, byte[] ChainCode) GetMasterKeyFromSeed(byte[] seed) + => HmacSha512(Encoding.UTF8.GetBytes(Curve), seed); + + /// + /// Computes the child key. + /// + /// The key used to derive from. + /// The chain code for derivation. + /// The index of the key to the derive. + /// A tuple consisting of the key and corresponding chain code. + private (byte[] Key, byte[] ChainCode) GetChildKeyDerivation(byte[] key, byte[] chainCode, uint index) + { + BigEndianBuffer buffer = new BigEndianBuffer(); + + buffer.Write(new byte[] { 0 }); + buffer.Write(key); + buffer.WriteUInt(index); + + return HmacSha512(chainCode, buffer.ToArray()); + } + + /// + /// Computes the HMAC SHA 512 of the byte array passed into data. + /// + /// The byte array to be used as the HMAC SHA512 key. + /// The data to calculate the HMAC SHA512 on. + /// A tuple consisting of the key and corresponding chain code. + private static (byte[] Key, byte[] ChainCode) HmacSha512(byte[] keyBuffer, byte[] data) + { + using var hmacSha512 = new HMACSHA512(keyBuffer); + var i = hmacSha512.ComputeHash(data); + + var il = i.Slice(0, 32); + var ir = i.Slice(32); + + return (Key: il, ChainCode: ir); + } + + /// + /// Checks if the derivation path is valid. + /// Returns true if the path is valid, otherwise false. + /// + /// The derivation path. + /// A boolean. + private static bool IsValidPath(string path) + { + var regex = new Regex("^m(\\/[0-9]+')+$"); + + if (!regex.IsMatch(path)) + return false; + + var valid = !(path.Split('/') + .Slice(1) + .Select(a => a.Replace("'", "")) + .Any(a => !int.TryParse(a, out _))); + + return valid; + } + + /// + /// Derives a child key from the passed derivation path. + /// + /// The derivation path. + /// The key and chaincode. + /// Thrown when the passed derivation path is invalid. + public (byte[] Key, byte[] ChainCode) DerivePath(string path) + { + if (!IsValidPath(path)) + throw new FormatException("Invalid derivation path"); + + var segments = path + .Split('/') + .Slice(1) + .Select(a => a.Replace("'", "")) + .Select(a => Convert.ToUInt32(a, 10)); + + var results = segments + .Aggregate( + (_masterKey, _chainCode), + (masterKeyFromSeed, next) => + GetChildKeyDerivation(masterKeyFromSeed._masterKey, masterKeyFromSeed._chainCode, next + HardenedOffset)); + + return results; + } + } } \ No newline at end of file diff --git a/src/Solnet.Wallet/Ed25519Extensions.cs b/src/Solnet.Wallet/Ed25519Extensions.cs index 8ce23d3e..ade3e390 100644 --- a/src/Solnet.Wallet/Ed25519Extensions.cs +++ b/src/Solnet.Wallet/Ed25519Extensions.cs @@ -1,15 +1,15 @@ -using Chaos.NaCl; - -namespace Solnet.Wallet -{ - public static class Ed25519Extensions - { - /// - /// Gets the corresponding ed25519 key pair from the passed seed. - /// - /// The seed - /// The key pair. - public static (byte[] privateKey, byte[] publicKey) EdKeyPairFromSeed(byte[] seed) => - new(Ed25519.ExpandedPrivateKeyFromSeed(seed), Ed25519.PublicKeyFromSeed(seed)); - } +using Chaos.NaCl; + +namespace Solnet.Wallet +{ + public static class Ed25519Extensions + { + /// + /// Gets the corresponding ed25519 key pair from the passed seed. + /// + /// The seed + /// The key pair. + public static (byte[] privateKey, byte[] publicKey) EdKeyPairFromSeed(byte[] seed) => + new(Ed25519.ExpandedPrivateKeyFromSeed(seed), Ed25519.PublicKeyFromSeed(seed)); + } } \ No newline at end of file diff --git a/src/Solnet.Wallet/SeedMode.cs b/src/Solnet.Wallet/SeedMode.cs index 3bcd1003..6a43331a 100644 --- a/src/Solnet.Wallet/SeedMode.cs +++ b/src/Solnet.Wallet/SeedMode.cs @@ -1,24 +1,24 @@ -namespace Solnet.Wallet -{ - /// - /// Specifies the available seed modes for key generation. - /// - /// Available modes: - /// - /// - /// - /// - public enum SeedMode - { - /// - /// Generates Ed25519 based BIP32 keys. - /// This seed mode is compatible with the keys generated in the Sollet/SPL Token Wallet, it does not use a passphrase to harden the mnemonic seed. - /// - Ed25519Bip32, - /// - /// Generates BIP39 keys. - /// This seed mode is compatible with the keys generated in the solana-keygen cli, it uses a passphrase to harden the mnemonic seed. - /// - Bip39 - } +namespace Solnet.Wallet +{ + /// + /// Specifies the available seed modes for key generation. + /// + /// Available modes: + /// + /// + /// + /// + public enum SeedMode + { + /// + /// Generates Ed25519 based BIP32 keys. + /// This seed mode is compatible with the keys generated in the Sollet/SPL Token Wallet, it does not use a passphrase to harden the mnemonic seed. + /// + Ed25519Bip32, + /// + /// Generates BIP39 keys. + /// This seed mode is compatible with the keys generated in the solana-keygen cli, it uses a passphrase to harden the mnemonic seed. + /// + Bip39 + } } \ No newline at end of file diff --git a/src/Solnet.Wallet/Utilities/BigEndianBuffer.cs b/src/Solnet.Wallet/Utilities/BigEndianBuffer.cs index 8e43dab4..b3b84f4a 100644 --- a/src/Solnet.Wallet/Utilities/BigEndianBuffer.cs +++ b/src/Solnet.Wallet/Utilities/BigEndianBuffer.cs @@ -1,42 +1,42 @@ -using System; -using System.Collections.Generic; - -namespace Solnet.Wallet.Utilities -{ - - public class BigEndianBuffer - { - private readonly List _bytes = new(); - - public void WriteUInt(uint i) - { - _bytes.Add((byte)((i >> 0x18) & 0xff)); - _bytes.Add((byte)((i >> 0x10) & 0xff)); - _bytes.Add((byte)((i >> 8) & 0xff)); - _bytes.Add((byte)(i & 0xff)); - } - - public void Write(byte b) - { - _bytes.Add(b); - } - - public void Write(byte[] bytes) - { - Write(bytes, 0, bytes.Length); - } - - private void Write(byte[] bytes, int offset, int count) - { - var newBytes = new byte[count]; - Array.Copy(bytes, offset, newBytes, 0, count); - - _bytes.AddRange(newBytes); - } - - public byte[] ToArray() - { - return _bytes.ToArray(); - } - } +using System; +using System.Collections.Generic; + +namespace Solnet.Wallet.Utilities +{ + + public class BigEndianBuffer + { + private readonly List _bytes = new(); + + public void WriteUInt(uint i) + { + _bytes.Add((byte)((i >> 0x18) & 0xff)); + _bytes.Add((byte)((i >> 0x10) & 0xff)); + _bytes.Add((byte)((i >> 8) & 0xff)); + _bytes.Add((byte)(i & 0xff)); + } + + public void Write(byte b) + { + _bytes.Add(b); + } + + public void Write(byte[] bytes) + { + Write(bytes, 0, bytes.Length); + } + + private void Write(byte[] bytes, int offset, int count) + { + var newBytes = new byte[count]; + Array.Copy(bytes, offset, newBytes, 0, count); + + _bytes.AddRange(newBytes); + } + + public byte[] ToArray() + { + return _bytes.ToArray(); + } + } } \ No newline at end of file diff --git a/src/Solnet.Wallet/Utilities/Utils.cs b/src/Solnet.Wallet/Utilities/Utils.cs index 6e2a0659..7de4ab73 100644 --- a/src/Solnet.Wallet/Utilities/Utils.cs +++ b/src/Solnet.Wallet/Utilities/Utils.cs @@ -1,40 +1,40 @@ -namespace Solnet.Wallet.Utilities -{ - public static class Utils - { - - - /// - /// Slices the array, returning a new array starting at start index and ending at end index. - /// - /// The array to slice. - /// The starting index of the slicing. - /// The ending index of the slicing. - /// The array type. - /// The sliced array. - public static T[] Slice(this T[] source, int start, int end) - { - if (end < 0) - end = source.Length; - - var len = end - start; - - // Return new array. - var res = new T[len]; - for (var i = 0; i < len; i++) res[i] = source[i + start]; - return res; - } - - /// - /// Slices the array, returning a new array starting at start. - /// - /// The array to slice. - /// The starting index of the slicing. - /// The array type. - /// The sliced array. - public static T[] Slice(this T[] source, int start) - { - return Slice(source, start, -1); - } - } +namespace Solnet.Wallet.Utilities +{ + public static class Utils + { + + + /// + /// Slices the array, returning a new array starting at start index and ending at end index. + /// + /// The array to slice. + /// The starting index of the slicing. + /// The ending index of the slicing. + /// The array type. + /// The sliced array. + public static T[] Slice(this T[] source, int start, int end) + { + if (end < 0) + end = source.Length; + + var len = end - start; + + // Return new array. + var res = new T[len]; + for (var i = 0; i < len; i++) res[i] = source[i + start]; + return res; + } + + /// + /// Slices the array, returning a new array starting at start. + /// + /// The array to slice. + /// The starting index of the slicing. + /// The array type. + /// The sliced array. + public static T[] Slice(this T[] source, int start) + { + return Slice(source, start, -1); + } + } } \ No newline at end of file diff --git a/src/Solnet.Wallet/Wallet.cs b/src/Solnet.Wallet/Wallet.cs index 83cf734a..7f205923 100644 --- a/src/Solnet.Wallet/Wallet.cs +++ b/src/Solnet.Wallet/Wallet.cs @@ -1,223 +1,223 @@ -using Chaos.NaCl; -using NBitcoin; -using System; -using System.Linq; - -namespace Solnet.Wallet -{ - /// - /// Represents a wallet. - /// - public class Wallet - { - /// - /// The derivation path. - /// - private const string DerivationPath = "m/44'/501'/x'/0'"; - - /// - /// The seed mode used for key generation. - /// - private readonly SeedMode _seedMode; - - /// - /// The seed derived from the mnemonic and/or passphrase. - /// - private byte[] _seed; - - /// - /// The method used for key generation. - /// - private Ed25519Bip32 _ed25519Bip32; - - /// - /// The passphrase string. - /// - private string Passphrase { get; } - - /// - /// The key pair. - /// - public Account Account { get; private set; } - - /// - /// The mnemonic words. - /// - // ReSharper disable once MemberCanBePrivate.Global - public Mnemonic Mnemonic { get; } - - /// - /// Initialize a wallet from passed word count and word list for the mnemonic and passphrase. - /// - /// The mnemonic word count. - /// The language of the mnemonic words. - /// The passphrase. - /// The seed generation mode. - public Wallet(WordCount wordCount, Wordlist wordlist, string passphrase = "", SeedMode seedMode = SeedMode.Ed25519Bip32) - : this(new Mnemonic(wordlist, wordCount), passphrase, seedMode) - { - } - - /// - /// Initialize a wallet from the passed mnemonic and passphrase. - /// - /// The mnemonic. - /// The passphrase. - /// The seed generation mode. - public Wallet(Mnemonic mnemonic, string passphrase = "", SeedMode seedMode = SeedMode.Ed25519Bip32) - { - Mnemonic = mnemonic; - Passphrase = passphrase; - - _seedMode = seedMode; - InitializeSeed(); - } - - /// - /// Initialize a wallet from the passed mnemonic and passphrase. - /// - /// The mnemonic words. - /// The language of the mnemonic words. Defaults to . - /// The passphrase. - /// The seed generation mode. - public Wallet(string mnemonicWords, Wordlist wordlist = null, string passphrase = "", SeedMode seedMode = SeedMode.Ed25519Bip32) - { - Mnemonic = new Mnemonic(mnemonicWords, wordlist ?? Wordlist.English); - Passphrase = passphrase; - - _seedMode = seedMode; - InitializeSeed(); - } - - /// - /// Initializes a wallet from the passed seed byte array. - /// - /// The seed used for key derivation. - /// The passphrase. - /// The seed mode. - public Wallet(byte[] seed, string passphrase = "", SeedMode seedMode = SeedMode.Ed25519Bip32) - { - if (seed.Length != Ed25519.ExpandedPrivateKeySizeInBytes) - throw new ArgumentException("invalid seed length", nameof(seed)); - - Passphrase = passphrase; - - _seedMode = seedMode; - _seed = seed; - InitializeFirstAccount(); - } - - /// - /// Verify the signed message. - /// - /// The signed message. - /// The signature of the message. - /// The index of the account used to verify the signed message. - /// - public bool Verify(byte[] message, byte[] signature, int accountIndex) - { - if (_seedMode != SeedMode.Ed25519Bip32) - throw new Exception("cannot verify bip39 signatures using ed25519 based bip32 keys"); - - var account = GetAccount(accountIndex); - return account.Verify(message, signature); - } - - /// - /// Verify the signed message. - /// - /// The signed message. - /// The signature of the message. - /// - public bool Verify(byte[] message, byte[] signature) - { - return Account.Verify(message, signature); - } - - /// - /// Sign the data. - /// - /// The data to sign. - /// The account used to sign the data. - /// The signature of the data. - public byte[] Sign(byte[] message, int accountIndex) - { - if (_seedMode != SeedMode.Ed25519Bip32) - throw new Exception("cannot compute bip39 signature using ed25519 based bip32 keys "); - - var account = GetAccount(accountIndex); - var signature = account.Sign(message); - - return signature.ToArray(); - } - - /// - /// Sign the data. - /// - /// The data to sign. - /// The signature of the data. - public byte[] Sign(byte[] message) - { - var signature = Account.Sign(message); - return signature.ToArray(); - } - - /// - /// Gets the account at the passed index using the ed25519 bip32 derivation path. - /// - /// The index of the account. - /// The account. - public Account GetAccount(int index) - { - if (_seedMode != SeedMode.Ed25519Bip32) - throw new Exception($"seed mode: {_seedMode} cannot derive Ed25519 based BIP32 keys"); - - var path = DerivationPath.Replace("x", index.ToString()); - var (account, chain) = _ed25519Bip32.DerivePath(path); - var (privateKey, publicKey) = Ed25519Extensions.EdKeyPairFromSeed(account); - return new Account(privateKey, publicKey); - } - - /// - /// Derive a seed from the passed mnemonic and/or passphrase, depending on . - /// - /// The seed. - public byte[] DeriveMnemonicSeed() - { - if (_seed != null) return _seed; - return _seedMode switch - { - SeedMode.Ed25519Bip32 => Mnemonic.DeriveSeed(), - SeedMode.Bip39 => Mnemonic.DeriveSeed(Passphrase), - _ => Mnemonic.DeriveSeed() - }; - } - - /// - /// Initializes the first account with a key pair derived from the initialized seed. - /// - private void InitializeFirstAccount() - { - if (_seedMode == SeedMode.Ed25519Bip32) - { - _ed25519Bip32 = new Ed25519Bip32(_seed); - Account = GetAccount(0); - } - else - { - var (privateKey, publicKey) = Ed25519Extensions.EdKeyPairFromSeed(_seed[..32]); - Account = new Account(privateKey, publicKey); - } - } - - /// - /// Derive the mnemonic seed and generate the key pair for the configured wallet. - /// - private void InitializeSeed() - { - _seed = DeriveMnemonicSeed(); - - InitializeFirstAccount(); - } - } +using Chaos.NaCl; +using NBitcoin; +using System; +using System.Linq; + +namespace Solnet.Wallet +{ + /// + /// Represents a wallet. + /// + public class Wallet + { + /// + /// The derivation path. + /// + private const string DerivationPath = "m/44'/501'/x'/0'"; + + /// + /// The seed mode used for key generation. + /// + private readonly SeedMode _seedMode; + + /// + /// The seed derived from the mnemonic and/or passphrase. + /// + private byte[] _seed; + + /// + /// The method used for key generation. + /// + private Ed25519Bip32 _ed25519Bip32; + + /// + /// The passphrase string. + /// + private string Passphrase { get; } + + /// + /// The key pair. + /// + public Account Account { get; private set; } + + /// + /// The mnemonic words. + /// + // ReSharper disable once MemberCanBePrivate.Global + public Mnemonic Mnemonic { get; } + + /// + /// Initialize a wallet from passed word count and word list for the mnemonic and passphrase. + /// + /// The mnemonic word count. + /// The language of the mnemonic words. + /// The passphrase. + /// The seed generation mode. + public Wallet(WordCount wordCount, Wordlist wordlist, string passphrase = "", SeedMode seedMode = SeedMode.Ed25519Bip32) + : this(new Mnemonic(wordlist, wordCount), passphrase, seedMode) + { + } + + /// + /// Initialize a wallet from the passed mnemonic and passphrase. + /// + /// The mnemonic. + /// The passphrase. + /// The seed generation mode. + public Wallet(Mnemonic mnemonic, string passphrase = "", SeedMode seedMode = SeedMode.Ed25519Bip32) + { + Mnemonic = mnemonic; + Passphrase = passphrase; + + _seedMode = seedMode; + InitializeSeed(); + } + + /// + /// Initialize a wallet from the passed mnemonic and passphrase. + /// + /// The mnemonic words. + /// The language of the mnemonic words. Defaults to . + /// The passphrase. + /// The seed generation mode. + public Wallet(string mnemonicWords, Wordlist wordlist = null, string passphrase = "", SeedMode seedMode = SeedMode.Ed25519Bip32) + { + Mnemonic = new Mnemonic(mnemonicWords, wordlist ?? Wordlist.English); + Passphrase = passphrase; + + _seedMode = seedMode; + InitializeSeed(); + } + + /// + /// Initializes a wallet from the passed seed byte array. + /// + /// The seed used for key derivation. + /// The passphrase. + /// The seed mode. + public Wallet(byte[] seed, string passphrase = "", SeedMode seedMode = SeedMode.Ed25519Bip32) + { + if (seed.Length != Ed25519.ExpandedPrivateKeySizeInBytes) + throw new ArgumentException("invalid seed length", nameof(seed)); + + Passphrase = passphrase; + + _seedMode = seedMode; + _seed = seed; + InitializeFirstAccount(); + } + + /// + /// Verify the signed message. + /// + /// The signed message. + /// The signature of the message. + /// The index of the account used to verify the signed message. + /// + public bool Verify(byte[] message, byte[] signature, int accountIndex) + { + if (_seedMode != SeedMode.Ed25519Bip32) + throw new Exception("cannot verify bip39 signatures using ed25519 based bip32 keys"); + + var account = GetAccount(accountIndex); + return account.Verify(message, signature); + } + + /// + /// Verify the signed message. + /// + /// The signed message. + /// The signature of the message. + /// + public bool Verify(byte[] message, byte[] signature) + { + return Account.Verify(message, signature); + } + + /// + /// Sign the data. + /// + /// The data to sign. + /// The account used to sign the data. + /// The signature of the data. + public byte[] Sign(byte[] message, int accountIndex) + { + if (_seedMode != SeedMode.Ed25519Bip32) + throw new Exception("cannot compute bip39 signature using ed25519 based bip32 keys "); + + var account = GetAccount(accountIndex); + var signature = account.Sign(message); + + return signature.ToArray(); + } + + /// + /// Sign the data. + /// + /// The data to sign. + /// The signature of the data. + public byte[] Sign(byte[] message) + { + var signature = Account.Sign(message); + return signature.ToArray(); + } + + /// + /// Gets the account at the passed index using the ed25519 bip32 derivation path. + /// + /// The index of the account. + /// The account. + public Account GetAccount(int index) + { + if (_seedMode != SeedMode.Ed25519Bip32) + throw new Exception($"seed mode: {_seedMode} cannot derive Ed25519 based BIP32 keys"); + + var path = DerivationPath.Replace("x", index.ToString()); + var (account, chain) = _ed25519Bip32.DerivePath(path); + var (privateKey, publicKey) = Ed25519Extensions.EdKeyPairFromSeed(account); + return new Account(privateKey, publicKey); + } + + /// + /// Derive a seed from the passed mnemonic and/or passphrase, depending on . + /// + /// The seed. + public byte[] DeriveMnemonicSeed() + { + if (_seed != null) return _seed; + return _seedMode switch + { + SeedMode.Ed25519Bip32 => Mnemonic.DeriveSeed(), + SeedMode.Bip39 => Mnemonic.DeriveSeed(Passphrase), + _ => Mnemonic.DeriveSeed() + }; + } + + /// + /// Initializes the first account with a key pair derived from the initialized seed. + /// + private void InitializeFirstAccount() + { + if (_seedMode == SeedMode.Ed25519Bip32) + { + _ed25519Bip32 = new Ed25519Bip32(_seed); + Account = GetAccount(0); + } + else + { + var (privateKey, publicKey) = Ed25519Extensions.EdKeyPairFromSeed(_seed[..32]); + Account = new Account(privateKey, publicKey); + } + } + + /// + /// Derive the mnemonic seed and generate the key pair for the configured wallet. + /// + private void InitializeSeed() + { + _seed = DeriveMnemonicSeed(); + + InitializeFirstAccount(); + } + } } \ No newline at end of file diff --git a/test/Solnet.KeyStore.Test/KeyStoreKdfCheckerTest.cs b/test/Solnet.KeyStore.Test/KeyStoreKdfCheckerTest.cs index 929aef7e..b5e7ccee 100644 --- a/test/Solnet.KeyStore.Test/KeyStoreKdfCheckerTest.cs +++ b/test/Solnet.KeyStore.Test/KeyStoreKdfCheckerTest.cs @@ -1,31 +1,31 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Solnet.KeyStore.Exceptions; - -namespace Solnet.KeyStore.Test -{ - [TestClass] - public class KeyStoreKdfCheckerTest - { - private const string ValidKdfCheckerPath = "Resources/ValidKdfType.json"; - private const string InvalidKdfCheckerPath = "Resources/InvalidKdfType.json"; - - private static readonly byte[] SeedWithPassphrase = - { - 163,4,184,24,182,219,174,214,13,54,158,198, - 63,202,76,3,190,224,76,202,160,96,124,95,89, - 155,113,10,46,218,154,74,125,7,103,78,0,51, - 244,192,221,12,200,148,9,252,4,117,193,123, - 102,56,255,105,167,180,125,222,19,111,219,18, - 115,0 - }; - - private static readonly SecretKeyStoreService KeyStore = new(); - - [TestMethod] - [ExpectedException(typeof(InvalidKdfException))] - public void TestInvalidKdf() - { - _ = KeyStore.DecryptKeyStoreFromFile("randomPassword", InvalidKdfCheckerPath); - } - } +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Solnet.KeyStore.Exceptions; + +namespace Solnet.KeyStore.Test +{ + [TestClass] + public class KeyStoreKdfCheckerTest + { + private const string ValidKdfCheckerPath = "Resources/ValidKdfType.json"; + private const string InvalidKdfCheckerPath = "Resources/InvalidKdfType.json"; + + private static readonly byte[] SeedWithPassphrase = + { + 163,4,184,24,182,219,174,214,13,54,158,198, + 63,202,76,3,190,224,76,202,160,96,124,95,89, + 155,113,10,46,218,154,74,125,7,103,78,0,51, + 244,192,221,12,200,148,9,252,4,117,193,123, + 102,56,255,105,167,180,125,222,19,111,219,18, + 115,0 + }; + + private static readonly SecretKeyStoreService KeyStore = new(); + + [TestMethod] + [ExpectedException(typeof(InvalidKdfException))] + public void TestInvalidKdf() + { + _ = KeyStore.DecryptKeyStoreFromFile("randomPassword", InvalidKdfCheckerPath); + } + } } \ No newline at end of file diff --git a/test/Solnet.KeyStore.Test/SecretKeyStoreServiceTest.cs b/test/Solnet.KeyStore.Test/SecretKeyStoreServiceTest.cs index dd646000..86fe9991 100644 --- a/test/Solnet.KeyStore.Test/SecretKeyStoreServiceTest.cs +++ b/test/Solnet.KeyStore.Test/SecretKeyStoreServiceTest.cs @@ -1,140 +1,140 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Solnet.KeyStore.Exceptions; -using Solnet.KeyStore.Services; -using System; -using System.IO; -using System.Text.Json; - -namespace Solnet.KeyStore.Test -{ - [TestClass] - public class SecretKeyStoreServiceTest - { - private const string InvalidPath = "Resources/DoesNotExist.json"; - private const string InvalidEmptyFilePath = "Resources/InvalidEmptyFile.json"; - private const string ValidKeyStorePath = "Resources/ValidKeyStore.json"; - private const string InvalidKeyStorePath = "Resources/InvalidKeyStore.json"; - private const string ValidPbkdf2KeyStorePath = "Resources/ValidPbkdf2KeyStore.json"; - private const string InvalidPbkdf2KeyStorePath = "Resources/InvalidPbkdf2KeyStore.json"; - - private static readonly byte[] SeedWithoutPassphrase = - { - 124,36,217,106,151,19,165,102,96,101,74,81, - 237,254,232,133,28,167,31,35,119,188,66,40, - 101,104,25,103,139,83,57,7,19,215,6,113,22, - 145,107,209,208,107,159,40,223,19,82,53,136, - 255,40,171,137,93,9,205,28,7,207,88,194,91, - 219,232 - }; - private static readonly byte[] SeedWithPassphrase = - { - 163,4,184,24,182,219,174,214,13,54,158,198, - 63,202,76,3,190,224,76,202,160,96,124,95,89, - 155,113,10,46,218,154,74,125,7,103,78,0,51, - 244,192,221,12,200,148,9,252,4,117,193,123, - 102,56,255,105,167,180,125,222,19,111,219,18, - 115,0 - }; - - private const string ExpectedKeyStoreAddress = "4n8BE7DHH4NudifUBrwPbvNPs2F86XcagT7C2JKdrWrR"; - - private static readonly SecretKeyStoreService KeyStore = new(); - - [TestMethod] - [ExpectedException(typeof(FileNotFoundException))] - public void TestKeyStorePathNotFound() - { - _ = KeyStore.DecryptKeyStoreFromFile("randomPassword", InvalidPath); - } - - [TestMethod] - [ExpectedException(typeof(JsonException))] - public void TestKeyStoreInvalidEmptyFilePath() - { - _ = KeyStore.DecryptKeyStoreFromFile("randomPassword", InvalidEmptyFilePath); - } - - [TestMethod] - public void TestKeyStoreValid() - { - var seed = KeyStore.DecryptKeyStoreFromFile("randomPassword", ValidKeyStorePath); - - CollectionAssert.AreEqual(SeedWithPassphrase, seed); - } - - [TestMethod] - [ExpectedException(typeof(DecryptionException))] - public void TestKeyStoreInvalidPassword() - { - _ = KeyStore.DecryptKeyStoreFromFile("randomPassworasdd", ValidKeyStorePath); - } - - [TestMethod] - [ExpectedException(typeof(ArgumentException))] - public void TestKeyStoreInvalid() - { - _ = KeyStore.DecryptKeyStoreFromFile("randomPassword", InvalidKeyStorePath); - } - - [TestMethod] - public void TestKeyStoreSerialize() - { - var json = KeyStore.EncryptAndGenerateDefaultKeyStoreAsJson("randomPassword", SeedWithPassphrase, - ExpectedKeyStoreAddress); - - var keyStoreAddress = SecretKeyStoreService.GetAddressFromKeyStore(json); - - Assert.AreEqual(ExpectedKeyStoreAddress, keyStoreAddress); - } - - [TestMethod] - public void TestKeyStoreGenerateKeyStore() - { - var json = KeyStore.EncryptAndGenerateDefaultKeyStoreAsJson("randomPassword", SeedWithPassphrase, - ExpectedKeyStoreAddress); - - var keyStoreAddress = SecretKeyStoreService.GetAddressFromKeyStore(json); - - Assert.AreEqual(ExpectedKeyStoreAddress, keyStoreAddress); - } - - [TestMethod] - public void TestKeyStoreGetAddress() - { - var fileJson = File.ReadAllText(ValidPbkdf2KeyStorePath); - var keyStoreAddress = SecretKeyStoreService.GetAddressFromKeyStore(fileJson); - - Assert.AreEqual(ExpectedKeyStoreAddress, keyStoreAddress); - } - - [TestMethod] - public void TestValidPbkdf2KeyStore() - { - var ks = new KeyStorePbkdf2Service(); - var fileJson = File.ReadAllText(ValidPbkdf2KeyStorePath); - var seed = ks.DecryptKeyStoreFromJson("randomPassword", fileJson); - CollectionAssert.AreEqual(SeedWithPassphrase, seed); - } - - [TestMethod] - public void TestValidPbkdf2KeyStoreSerialize() - { - var ks = new KeyStorePbkdf2Service(); - var json = ks.EncryptAndGenerateKeyStoreAsJson("randomPassword", SeedWithPassphrase, - ExpectedKeyStoreAddress); - - var keyStoreAddress = SecretKeyStoreService.GetAddressFromKeyStore(json); - - Assert.AreEqual(ExpectedKeyStoreAddress, keyStoreAddress); - } - - [TestMethod] - [ExpectedException(typeof(ArgumentException))] - public void TestInvalidPbkdf2KeyStore() - { - var ks = new KeyStorePbkdf2Service(); - var fileJson = File.ReadAllText(InvalidPbkdf2KeyStorePath); - _ = ks.DecryptKeyStoreFromJson("randomPassword", fileJson); - } - } +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Solnet.KeyStore.Exceptions; +using Solnet.KeyStore.Services; +using System; +using System.IO; +using System.Text.Json; + +namespace Solnet.KeyStore.Test +{ + [TestClass] + public class SecretKeyStoreServiceTest + { + private const string InvalidPath = "Resources/DoesNotExist.json"; + private const string InvalidEmptyFilePath = "Resources/InvalidEmptyFile.json"; + private const string ValidKeyStorePath = "Resources/ValidKeyStore.json"; + private const string InvalidKeyStorePath = "Resources/InvalidKeyStore.json"; + private const string ValidPbkdf2KeyStorePath = "Resources/ValidPbkdf2KeyStore.json"; + private const string InvalidPbkdf2KeyStorePath = "Resources/InvalidPbkdf2KeyStore.json"; + + private static readonly byte[] SeedWithoutPassphrase = + { + 124,36,217,106,151,19,165,102,96,101,74,81, + 237,254,232,133,28,167,31,35,119,188,66,40, + 101,104,25,103,139,83,57,7,19,215,6,113,22, + 145,107,209,208,107,159,40,223,19,82,53,136, + 255,40,171,137,93,9,205,28,7,207,88,194,91, + 219,232 + }; + private static readonly byte[] SeedWithPassphrase = + { + 163,4,184,24,182,219,174,214,13,54,158,198, + 63,202,76,3,190,224,76,202,160,96,124,95,89, + 155,113,10,46,218,154,74,125,7,103,78,0,51, + 244,192,221,12,200,148,9,252,4,117,193,123, + 102,56,255,105,167,180,125,222,19,111,219,18, + 115,0 + }; + + private const string ExpectedKeyStoreAddress = "4n8BE7DHH4NudifUBrwPbvNPs2F86XcagT7C2JKdrWrR"; + + private static readonly SecretKeyStoreService KeyStore = new(); + + [TestMethod] + [ExpectedException(typeof(FileNotFoundException))] + public void TestKeyStorePathNotFound() + { + _ = KeyStore.DecryptKeyStoreFromFile("randomPassword", InvalidPath); + } + + [TestMethod] + [ExpectedException(typeof(JsonException))] + public void TestKeyStoreInvalidEmptyFilePath() + { + _ = KeyStore.DecryptKeyStoreFromFile("randomPassword", InvalidEmptyFilePath); + } + + [TestMethod] + public void TestKeyStoreValid() + { + var seed = KeyStore.DecryptKeyStoreFromFile("randomPassword", ValidKeyStorePath); + + CollectionAssert.AreEqual(SeedWithPassphrase, seed); + } + + [TestMethod] + [ExpectedException(typeof(DecryptionException))] + public void TestKeyStoreInvalidPassword() + { + _ = KeyStore.DecryptKeyStoreFromFile("randomPassworasdd", ValidKeyStorePath); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void TestKeyStoreInvalid() + { + _ = KeyStore.DecryptKeyStoreFromFile("randomPassword", InvalidKeyStorePath); + } + + [TestMethod] + public void TestKeyStoreSerialize() + { + var json = KeyStore.EncryptAndGenerateDefaultKeyStoreAsJson("randomPassword", SeedWithPassphrase, + ExpectedKeyStoreAddress); + + var keyStoreAddress = SecretKeyStoreService.GetAddressFromKeyStore(json); + + Assert.AreEqual(ExpectedKeyStoreAddress, keyStoreAddress); + } + + [TestMethod] + public void TestKeyStoreGenerateKeyStore() + { + var json = KeyStore.EncryptAndGenerateDefaultKeyStoreAsJson("randomPassword", SeedWithPassphrase, + ExpectedKeyStoreAddress); + + var keyStoreAddress = SecretKeyStoreService.GetAddressFromKeyStore(json); + + Assert.AreEqual(ExpectedKeyStoreAddress, keyStoreAddress); + } + + [TestMethod] + public void TestKeyStoreGetAddress() + { + var fileJson = File.ReadAllText(ValidPbkdf2KeyStorePath); + var keyStoreAddress = SecretKeyStoreService.GetAddressFromKeyStore(fileJson); + + Assert.AreEqual(ExpectedKeyStoreAddress, keyStoreAddress); + } + + [TestMethod] + public void TestValidPbkdf2KeyStore() + { + var ks = new KeyStorePbkdf2Service(); + var fileJson = File.ReadAllText(ValidPbkdf2KeyStorePath); + var seed = ks.DecryptKeyStoreFromJson("randomPassword", fileJson); + CollectionAssert.AreEqual(SeedWithPassphrase, seed); + } + + [TestMethod] + public void TestValidPbkdf2KeyStoreSerialize() + { + var ks = new KeyStorePbkdf2Service(); + var json = ks.EncryptAndGenerateKeyStoreAsJson("randomPassword", SeedWithPassphrase, + ExpectedKeyStoreAddress); + + var keyStoreAddress = SecretKeyStoreService.GetAddressFromKeyStore(json); + + Assert.AreEqual(ExpectedKeyStoreAddress, keyStoreAddress); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void TestInvalidPbkdf2KeyStore() + { + var ks = new KeyStorePbkdf2Service(); + var fileJson = File.ReadAllText(InvalidPbkdf2KeyStorePath); + _ = ks.DecryptKeyStoreFromJson("randomPassword", fileJson); + } + } } \ No newline at end of file diff --git a/test/Solnet.KeyStore.Test/SolanaKeygenKeyStoreTest.cs b/test/Solnet.KeyStore.Test/SolanaKeygenKeyStoreTest.cs index 025c6ffc..50bcfd8a 100644 --- a/test/Solnet.KeyStore.Test/SolanaKeygenKeyStoreTest.cs +++ b/test/Solnet.KeyStore.Test/SolanaKeygenKeyStoreTest.cs @@ -1,91 +1,91 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Solnet.Wallet; -using System; -using System.IO; - -namespace Solnet.KeyStore.Test -{ - [TestClass] - public class SolanaKeygenKeyStoreTest - { - private const string InvalidPath = "Resources/DoesNotExist.txt"; - private const string InvalidEmptyFilePath = "Resources/InvalidEmptyFile.txt"; - private const string ValidKeyStorePath = "Resources/ValidSolanaKeygenKeyStore.txt"; - private const string InvalidKeyStorePath = "Resources/InvalidSolanaKeygenKeyStore.txt"; - private const string ValidKeyStoreSavePath = "Resources/ValidSolanaKeygenSave.txt"; - - private const string ExpectedKeyStoreAddress = "4n8BE7DHH4NudifUBrwPbvNPs2F86XcagT7C2JKdrWrR"; - - private static readonly byte[] SeedWithPassphrase = - { - 163,4,184,24,182,219,174,214,13,54,158,198, - 63,202,76,3,190,224,76,202,160,96,124,95,89, - 155,113,10,46,218,154,74,125,7,103,78,0,51, - 244,192,221,12,200,148,9,252,4,117,193,123, - 102,56,255,105,167,180,125,222,19,111,219,18, - 115,0 - }; - - [TestMethod] - [ExpectedException(typeof(FileNotFoundException))] - public void TestKeyStoreFileNotFound() - { - var keyStore = new SolanaKeyStoreService(); - _ = keyStore.RestoreKeystore(InvalidPath); - } - - [TestMethod] - [ExpectedException(typeof(ArgumentOutOfRangeException))] - public void TestKeyStoreInvalidEmptyFilePath() - { - var keyStore = new SolanaKeyStoreService(); - _ = keyStore.RestoreKeystore(InvalidEmptyFilePath); - } - - [TestMethod] - public void TestKeyStoreValid() - { - var keyStore = new SolanaKeyStoreService(); - var wallet = keyStore.RestoreKeystore(ValidKeyStorePath); - - Assert.AreEqual(wallet.Account.GetPublicKey, ExpectedKeyStoreAddress); - } - - [TestMethod] - [ExpectedException(typeof(ArgumentException))] - public void TestKeyStoreInvalid() - { - var keyStore = new SolanaKeyStoreService(); - _ = keyStore.RestoreKeystore(InvalidKeyStorePath); - } - - [TestMethod] - public void TestKeyStoreFull() - { - var keyStore = new SolanaKeyStoreService(); - var walletToSave = new Wallet.Wallet(SeedWithPassphrase, "bip39passphrase", SeedMode.Bip39); - keyStore.SaveKeystore(ValidKeyStoreSavePath, walletToSave); - var restoredWallet = keyStore.RestoreKeystore(ValidKeyStorePath, "bip39passphrase"); - - Assert.AreEqual(ExpectedKeyStoreAddress, walletToSave.Account.GetPublicKey); - Assert.AreEqual(ExpectedKeyStoreAddress, restoredWallet.Account.GetPublicKey); - } - - [TestMethod] - [ExpectedException(typeof(NotImplementedException))] - public void TestKeyStoreDecryptNotImplemented() - { - - var keyStore = new SolanaKeyStoreService(); - _ = keyStore.DecryptAndRestoreKeystore("Some/Path"); - } - - [TestMethod] - [ExpectedException(typeof(NotImplementedException))] - public void TestKeyStoreEncryptNotImplemented() - { - var keyStore = new SolanaKeyStoreService(); - keyStore.EncryptAndSaveKeystore("Some/Path", new Wallet.Wallet(SeedWithPassphrase)); - } - } +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Solnet.Wallet; +using System; +using System.IO; + +namespace Solnet.KeyStore.Test +{ + [TestClass] + public class SolanaKeygenKeyStoreTest + { + private const string InvalidPath = "Resources/DoesNotExist.txt"; + private const string InvalidEmptyFilePath = "Resources/InvalidEmptyFile.txt"; + private const string ValidKeyStorePath = "Resources/ValidSolanaKeygenKeyStore.txt"; + private const string InvalidKeyStorePath = "Resources/InvalidSolanaKeygenKeyStore.txt"; + private const string ValidKeyStoreSavePath = "Resources/ValidSolanaKeygenSave.txt"; + + private const string ExpectedKeyStoreAddress = "4n8BE7DHH4NudifUBrwPbvNPs2F86XcagT7C2JKdrWrR"; + + private static readonly byte[] SeedWithPassphrase = + { + 163,4,184,24,182,219,174,214,13,54,158,198, + 63,202,76,3,190,224,76,202,160,96,124,95,89, + 155,113,10,46,218,154,74,125,7,103,78,0,51, + 244,192,221,12,200,148,9,252,4,117,193,123, + 102,56,255,105,167,180,125,222,19,111,219,18, + 115,0 + }; + + [TestMethod] + [ExpectedException(typeof(FileNotFoundException))] + public void TestKeyStoreFileNotFound() + { + var keyStore = new SolanaKeyStoreService(); + _ = keyStore.RestoreKeystore(InvalidPath); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void TestKeyStoreInvalidEmptyFilePath() + { + var keyStore = new SolanaKeyStoreService(); + _ = keyStore.RestoreKeystore(InvalidEmptyFilePath); + } + + [TestMethod] + public void TestKeyStoreValid() + { + var keyStore = new SolanaKeyStoreService(); + var wallet = keyStore.RestoreKeystore(ValidKeyStorePath); + + Assert.AreEqual(wallet.Account.GetPublicKey, ExpectedKeyStoreAddress); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void TestKeyStoreInvalid() + { + var keyStore = new SolanaKeyStoreService(); + _ = keyStore.RestoreKeystore(InvalidKeyStorePath); + } + + [TestMethod] + public void TestKeyStoreFull() + { + var keyStore = new SolanaKeyStoreService(); + var walletToSave = new Wallet.Wallet(SeedWithPassphrase, "bip39passphrase", SeedMode.Bip39); + keyStore.SaveKeystore(ValidKeyStoreSavePath, walletToSave); + var restoredWallet = keyStore.RestoreKeystore(ValidKeyStorePath, "bip39passphrase"); + + Assert.AreEqual(ExpectedKeyStoreAddress, walletToSave.Account.GetPublicKey); + Assert.AreEqual(ExpectedKeyStoreAddress, restoredWallet.Account.GetPublicKey); + } + + [TestMethod] + [ExpectedException(typeof(NotImplementedException))] + public void TestKeyStoreDecryptNotImplemented() + { + + var keyStore = new SolanaKeyStoreService(); + _ = keyStore.DecryptAndRestoreKeystore("Some/Path"); + } + + [TestMethod] + [ExpectedException(typeof(NotImplementedException))] + public void TestKeyStoreEncryptNotImplemented() + { + var keyStore = new SolanaKeyStoreService(); + keyStore.EncryptAndSaveKeystore("Some/Path", new Wallet.Wallet(SeedWithPassphrase)); + } + } } \ No newline at end of file diff --git a/test/Solnet.Programs.Test/SystemProgramTest.cs b/test/Solnet.Programs.Test/SystemProgramTest.cs index 88f6182d..91e3c582 100644 --- a/test/Solnet.Programs.Test/SystemProgramTest.cs +++ b/test/Solnet.Programs.Test/SystemProgramTest.cs @@ -1,75 +1,75 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using NBitcoin.DataEncoders; - -namespace Solnet.Programs.Test -{ - [TestClass] - public class SystemProgramTest - { - private static readonly Base58Encoder Encoder = new(); - - private const string MnemonicWords = - "route clerk disease box emerge airport loud waste attitude film army tray " + - "forward deal onion eight catalog surface unit card window walnut wealth medal"; - - private const string CreateAccountInstructionBase64 = - "AAAAAPAdHwAAAAAApQAAAAAAAAAG3fbh12Whk9nL4UbO63msHLSF7V9bN5E6jPWFfv8AqQ=="; - - private static readonly byte[] SystemProgramIdBytes = - { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - }; - - private static readonly byte[] CreateAccountInstructionBytes = - { - 0, 0, 0, 0, 240, 29, 31, 0, 0, 0, 0, 0, 165, - 0, 0, 0, 0, 0, 0, 0, 6, 221, 246, 225, 215, - 101, 161, 147, 217, 203, 225, 70, 206, - 235, 121, 172, 28, 180, 133, 237, 95, 91, - 55, 145, 58, 140, 245, 133, 126, 255, 0, 169 - }; - - private static readonly byte[] TransferInstructionBytes = - { - 2, 0, 0, 0, 128, 150, 152, 0, 0, 0, 0, 0 - }; - - private const long BalanceForRentExemption = 2039280L; - - [TestMethod] - public void TestSystemProgramTransfer() - { - var wallet = new Wallet.Wallet(MnemonicWords); - - var fromAccount = wallet.GetAccount(0); - var toAccount = wallet.GetAccount(1); - - var txInstruction = SystemProgram.Transfer(fromAccount.GetPublicKey, toAccount.GetPublicKey, 10000000); - - Assert.AreEqual(2, txInstruction.Keys.Count); - CollectionAssert.AreEqual(TransferInstructionBytes, txInstruction.Data); - CollectionAssert.AreEqual(SystemProgramIdBytes, txInstruction.ProgramId); - } - - [TestMethod] - public void TestSystemProgramCreateAccount() - { - var wallet = new Wallet.Wallet(MnemonicWords); - - var mintAccount = wallet.GetAccount(3); - var ownerAccount = wallet.GetAccount(4); - - var txInstruction = SystemProgram.CreateAccount( - ownerAccount.GetPublicKey, - mintAccount.GetPublicKey, - BalanceForRentExemption, - SystemProgram.AccountDataSize, - TokenProgram.ProgramId); - - Assert.AreEqual(2, txInstruction.Keys.Count); - CollectionAssert.AreEqual(CreateAccountInstructionBytes, txInstruction.Data); - CollectionAssert.AreEqual(SystemProgramIdBytes, txInstruction.ProgramId); - } - } +using Microsoft.VisualStudio.TestTools.UnitTesting; +using NBitcoin.DataEncoders; + +namespace Solnet.Programs.Test +{ + [TestClass] + public class SystemProgramTest + { + private static readonly Base58Encoder Encoder = new(); + + private const string MnemonicWords = + "route clerk disease box emerge airport loud waste attitude film army tray " + + "forward deal onion eight catalog surface unit card window walnut wealth medal"; + + private const string CreateAccountInstructionBase64 = + "AAAAAPAdHwAAAAAApQAAAAAAAAAG3fbh12Whk9nL4UbO63msHLSF7V9bN5E6jPWFfv8AqQ=="; + + private static readonly byte[] SystemProgramIdBytes = + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + + private static readonly byte[] CreateAccountInstructionBytes = + { + 0, 0, 0, 0, 240, 29, 31, 0, 0, 0, 0, 0, 165, + 0, 0, 0, 0, 0, 0, 0, 6, 221, 246, 225, 215, + 101, 161, 147, 217, 203, 225, 70, 206, + 235, 121, 172, 28, 180, 133, 237, 95, 91, + 55, 145, 58, 140, 245, 133, 126, 255, 0, 169 + }; + + private static readonly byte[] TransferInstructionBytes = + { + 2, 0, 0, 0, 128, 150, 152, 0, 0, 0, 0, 0 + }; + + private const long BalanceForRentExemption = 2039280L; + + [TestMethod] + public void TestSystemProgramTransfer() + { + var wallet = new Wallet.Wallet(MnemonicWords); + + var fromAccount = wallet.GetAccount(0); + var toAccount = wallet.GetAccount(1); + + var txInstruction = SystemProgram.Transfer(fromAccount.GetPublicKey, toAccount.GetPublicKey, 10000000); + + Assert.AreEqual(2, txInstruction.Keys.Count); + CollectionAssert.AreEqual(TransferInstructionBytes, txInstruction.Data); + CollectionAssert.AreEqual(SystemProgramIdBytes, txInstruction.ProgramId); + } + + [TestMethod] + public void TestSystemProgramCreateAccount() + { + var wallet = new Wallet.Wallet(MnemonicWords); + + var mintAccount = wallet.GetAccount(3); + var ownerAccount = wallet.GetAccount(4); + + var txInstruction = SystemProgram.CreateAccount( + ownerAccount.GetPublicKey, + mintAccount.GetPublicKey, + BalanceForRentExemption, + SystemProgram.AccountDataSize, + TokenProgram.ProgramId); + + Assert.AreEqual(2, txInstruction.Keys.Count); + CollectionAssert.AreEqual(CreateAccountInstructionBytes, txInstruction.Data); + CollectionAssert.AreEqual(SystemProgramIdBytes, txInstruction.ProgramId); + } + } } \ No newline at end of file diff --git a/test/Solnet.Rpc.Test/ClientFactoryTest.cs b/test/Solnet.Rpc.Test/ClientFactoryTest.cs index 489696ae..183a5f76 100644 --- a/test/Solnet.Rpc.Test/ClientFactoryTest.cs +++ b/test/Solnet.Rpc.Test/ClientFactoryTest.cs @@ -1,44 +1,44 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace Solnet.Rpc.Test -{ - [TestClass] - public class ClientFactoryTest - { - [TestMethod] - public void BuilRpcClient() - { - var c = ClientFactory.GetClient(Cluster.DevNet); - - Assert.IsInstanceOfType(c, typeof(SolanaRpcClient)); - } - - [TestMethod] - public void BuilStreamingRpcClient() - { - var c = ClientFactory.GetStreamingClient(Cluster.DevNet); - - Assert.IsInstanceOfType(c, typeof(SolanaStreamingRpcClient)); - } - - [TestMethod] - public void BuilRpcClientFromString() - { - string url = "https://testnet.solana.com"; - var c = ClientFactory.GetClient(url); - - Assert.IsInstanceOfType(c, typeof(SolanaRpcClient)); - Assert.AreEqual(url, c.NodeAddress.OriginalString); - } - - [TestMethod] - public void BuilStreamingRpcClientFromString() - { - string url = "wss://api.testnet.solana.com"; - var c = ClientFactory.GetStreamingClient(url); - - Assert.IsInstanceOfType(c, typeof(SolanaStreamingRpcClient)); - Assert.AreEqual(url, c.NodeAddress.OriginalString); - } - } +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Solnet.Rpc.Test +{ + [TestClass] + public class ClientFactoryTest + { + [TestMethod] + public void BuilRpcClient() + { + var c = ClientFactory.GetClient(Cluster.DevNet); + + Assert.IsInstanceOfType(c, typeof(SolanaRpcClient)); + } + + [TestMethod] + public void BuilStreamingRpcClient() + { + var c = ClientFactory.GetStreamingClient(Cluster.DevNet); + + Assert.IsInstanceOfType(c, typeof(SolanaStreamingRpcClient)); + } + + [TestMethod] + public void BuilRpcClientFromString() + { + string url = "https://testnet.solana.com"; + var c = ClientFactory.GetClient(url); + + Assert.IsInstanceOfType(c, typeof(SolanaRpcClient)); + Assert.AreEqual(url, c.NodeAddress.OriginalString); + } + + [TestMethod] + public void BuilStreamingRpcClientFromString() + { + string url = "wss://api.testnet.solana.com"; + var c = ClientFactory.GetStreamingClient(url); + + Assert.IsInstanceOfType(c, typeof(SolanaStreamingRpcClient)); + Assert.AreEqual(url, c.NodeAddress.OriginalString); + } + } } \ No newline at end of file diff --git a/test/Solnet.Rpc.Test/SolanaRpcClientAccountTests.cs b/test/Solnet.Rpc.Test/SolanaRpcClientAccountTests.cs index acafd1c2..e1b26b8e 100644 --- a/test/Solnet.Rpc.Test/SolanaRpcClientAccountTests.cs +++ b/test/Solnet.Rpc.Test/SolanaRpcClientAccountTests.cs @@ -1,152 +1,152 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net.Http; -using System.Text; -using System.Threading.Tasks; - -namespace Solnet.Rpc.Test -{ - public partial class SolanaRpcClientTest - { - [TestMethod] - public void TestGetAccountInfoDefault() - { - var responseData = File.ReadAllText("Resources/Http/Accounts/GetAccountInfoResponse.json"); - var requestData = File.ReadAllText("Resources/Http/Accounts/GetAccountInfoRequest.json"); - - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri, - }; - - var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); - - var result = sut.GetAccountInfo("9we6kjtbcZ2vy3GSLLsZTEhbAqXPTRvEyoxa8wxSqKp5"); - Assert.AreEqual(requestData, sentMessage); - Assert.IsNotNull(result.Result); - Assert.IsTrue(result.WasSuccessful); - Assert.AreEqual(79200467UL, result.Result.Context.Slot); - Assert.AreEqual("", result.Result.Value.Data[0]); - Assert.AreEqual("base64", result.Result.Value.Data[1]); - Assert.AreEqual(false, result.Result.Value.Executable); - Assert.AreEqual(5478840UL, result.Result.Value.Lamports); - Assert.AreEqual("11111111111111111111111111111111", result.Result.Value.Owner); - Assert.AreEqual(195UL, result.Result.Value.RentEpoch); - - FinishTest(messageHandlerMock, TestnetUri); - } - - [TestMethod] - public void TestGetAccountInfoConfirmed() - { - var responseData = File.ReadAllText("Resources/Http/Accounts/GetAccountInfoResponse.json"); - var requestData = File.ReadAllText("Resources/Http/Accounts/GetAccountInfoConfirmedRequest.json"); - - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri, - }; - - var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); - - var result = sut.GetAccountInfo("9we6kjtbcZ2vy3GSLLsZTEhbAqXPTRvEyoxa8wxSqKp5", Types.Commitment.Confirmed); - Assert.AreEqual(requestData, sentMessage); - Assert.IsNotNull(result.Result); - Assert.IsTrue(result.WasSuccessful); - Assert.AreEqual(79200467UL, result.Result.Context.Slot); - Assert.AreEqual("", result.Result.Value.Data[0]); - Assert.AreEqual("base64", result.Result.Value.Data[1]); - Assert.AreEqual(false, result.Result.Value.Executable); - Assert.AreEqual(5478840UL, result.Result.Value.Lamports); - Assert.AreEqual("11111111111111111111111111111111", result.Result.Value.Owner); - Assert.AreEqual(195UL, result.Result.Value.RentEpoch); - - FinishTest(messageHandlerMock, TestnetUri); - } - - - [TestMethod] - public void TestGetProgramAccounts() - { - var responseData = File.ReadAllText("Resources/Http/Accounts/GetProgramAccountsResponse.json"); - var requestData = File.ReadAllText("Resources/Http/Accounts/GetProgramAccountsRequest.json"); - - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri, - }; - - var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); - - var result = sut.GetProgramAccounts("GrAkKfEpTKQuVHG2Y97Y2FF4i7y7Q5AHLK94JBy7Y5yv"); - Assert.AreEqual(requestData, sentMessage); - Assert.IsNotNull(result.Result); - Assert.IsTrue(result.WasSuccessful); - Assert.AreEqual(2, result.Result.Count); - Assert.AreEqual("FzNKvS4SCHDoNbnnfhmGSLVRCLNBUuGecxdvobSGmWMh", result.Result[0].PublicKey); - - Assert.AreEqual("NhOiFR2mEcZJFj1ciaG2IrWOf2poe4LNGYC5gvdULBYyFH1Kq4cdNyYf+7u2r6NaWXHwnqiXnCzkFhIDU" - + "jSbNN2i/bmtSgasAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADkpoamWb2mUaHqREQNm8VPcqSWUGCgPjWK" - + "jh0raCI+OEo8UAXpyc1w/8KV64XXwhGP70z6aN3K1vnzjpYXQqr3vvsgJ4UD4OatRY1IsR9NYTReSKpRIhPpTupzQ9W" - + "zTpfWSTLZP2xvdcWyo8spQGJ2uGX0jH9h4ZxJ+orI/IsnqxyAHH+MXZuMBl28YfgFJRh8PZHPKbmFvVPDFs3xgBVWzz" - + "QuNTAlY5aWAEN5CRqkYmOXDcge++gRlEry6ItrMEA0VZV0zsOFk2oDiT9W7slB3JefUOpWS4DMPJW6N0zRUDTtXaGmW" - + "rqt6W4vEGC0DnBI++A2ZkHoMmJ+qeCKBVkNJgAAADc4o2AAAAAA/w==", result.Result[0].Account.Data[0]); - Assert.AreEqual("base64", result.Result[0].Account.Data[1]); - Assert.AreEqual(false, result.Result[0].Account.Executable); - Assert.AreEqual(3486960UL, result.Result[0].Account.Lamports); - Assert.AreEqual("GrAkKfEpTKQuVHG2Y97Y2FF4i7y7Q5AHLK94JBy7Y5yv", result.Result[0].Account.Owner); - Assert.AreEqual(188UL, result.Result[0].Account.RentEpoch); - - FinishTest(messageHandlerMock, TestnetUri); - } - - - [TestMethod] - public void TestGetMultipleAccounts() - { - var responseData = File.ReadAllText("Resources/Http/Accounts/GetMultipleAccountsResponse.json"); - var requestData = File.ReadAllText("Resources/Http/Accounts/GetMultipleAccountsRequest.json"); - - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri, - }; - - var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); - - var result = sut.GetMultipleAccounts(new List { "Bbe9EKucmRtJr2J4dd5Eb5ybQmY7Fm7jYxKXxmmkLFsu", "9we6kjtbcZ2vy3GSLLsZTEhbAqXPTRvEyoxa8wxSqKp5" }); - Assert.AreEqual(requestData, sentMessage); - Assert.IsNotNull(result.Result); - Assert.IsTrue(result.WasSuccessful); - Assert.AreEqual(2, result.Result.Value.Count); - - Assert.AreEqual("base64", result.Result.Value[0].Data[1]); - Assert.AreEqual("", result.Result.Value[0].Data[0]); - Assert.AreEqual(false, result.Result.Value[0].Executable); - Assert.AreEqual(503668985208UL, result.Result.Value[0].Lamports); - Assert.AreEqual("11111111111111111111111111111111", result.Result.Value[0].Owner); - Assert.AreEqual(197UL, result.Result.Value[0].RentEpoch); - - FinishTest(messageHandlerMock, TestnetUri); - } - } -} +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; + +namespace Solnet.Rpc.Test +{ + public partial class SolanaRpcClientTest + { + [TestMethod] + public void TestGetAccountInfoDefault() + { + var responseData = File.ReadAllText("Resources/Http/Accounts/GetAccountInfoResponse.json"); + var requestData = File.ReadAllText("Resources/Http/Accounts/GetAccountInfoRequest.json"); + + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri, + }; + + var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); + + var result = sut.GetAccountInfo("9we6kjtbcZ2vy3GSLLsZTEhbAqXPTRvEyoxa8wxSqKp5"); + Assert.AreEqual(requestData, sentMessage); + Assert.IsNotNull(result.Result); + Assert.IsTrue(result.WasSuccessful); + Assert.AreEqual(79200467UL, result.Result.Context.Slot); + Assert.AreEqual("", result.Result.Value.Data[0]); + Assert.AreEqual("base64", result.Result.Value.Data[1]); + Assert.AreEqual(false, result.Result.Value.Executable); + Assert.AreEqual(5478840UL, result.Result.Value.Lamports); + Assert.AreEqual("11111111111111111111111111111111", result.Result.Value.Owner); + Assert.AreEqual(195UL, result.Result.Value.RentEpoch); + + FinishTest(messageHandlerMock, TestnetUri); + } + + [TestMethod] + public void TestGetAccountInfoConfirmed() + { + var responseData = File.ReadAllText("Resources/Http/Accounts/GetAccountInfoResponse.json"); + var requestData = File.ReadAllText("Resources/Http/Accounts/GetAccountInfoConfirmedRequest.json"); + + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri, + }; + + var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); + + var result = sut.GetAccountInfo("9we6kjtbcZ2vy3GSLLsZTEhbAqXPTRvEyoxa8wxSqKp5", Types.Commitment.Confirmed); + Assert.AreEqual(requestData, sentMessage); + Assert.IsNotNull(result.Result); + Assert.IsTrue(result.WasSuccessful); + Assert.AreEqual(79200467UL, result.Result.Context.Slot); + Assert.AreEqual("", result.Result.Value.Data[0]); + Assert.AreEqual("base64", result.Result.Value.Data[1]); + Assert.AreEqual(false, result.Result.Value.Executable); + Assert.AreEqual(5478840UL, result.Result.Value.Lamports); + Assert.AreEqual("11111111111111111111111111111111", result.Result.Value.Owner); + Assert.AreEqual(195UL, result.Result.Value.RentEpoch); + + FinishTest(messageHandlerMock, TestnetUri); + } + + + [TestMethod] + public void TestGetProgramAccounts() + { + var responseData = File.ReadAllText("Resources/Http/Accounts/GetProgramAccountsResponse.json"); + var requestData = File.ReadAllText("Resources/Http/Accounts/GetProgramAccountsRequest.json"); + + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri, + }; + + var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); + + var result = sut.GetProgramAccounts("GrAkKfEpTKQuVHG2Y97Y2FF4i7y7Q5AHLK94JBy7Y5yv"); + Assert.AreEqual(requestData, sentMessage); + Assert.IsNotNull(result.Result); + Assert.IsTrue(result.WasSuccessful); + Assert.AreEqual(2, result.Result.Count); + Assert.AreEqual("FzNKvS4SCHDoNbnnfhmGSLVRCLNBUuGecxdvobSGmWMh", result.Result[0].PublicKey); + + Assert.AreEqual("NhOiFR2mEcZJFj1ciaG2IrWOf2poe4LNGYC5gvdULBYyFH1Kq4cdNyYf+7u2r6NaWXHwnqiXnCzkFhIDU" + + "jSbNN2i/bmtSgasAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADkpoamWb2mUaHqREQNm8VPcqSWUGCgPjWK" + + "jh0raCI+OEo8UAXpyc1w/8KV64XXwhGP70z6aN3K1vnzjpYXQqr3vvsgJ4UD4OatRY1IsR9NYTReSKpRIhPpTupzQ9W" + + "zTpfWSTLZP2xvdcWyo8spQGJ2uGX0jH9h4ZxJ+orI/IsnqxyAHH+MXZuMBl28YfgFJRh8PZHPKbmFvVPDFs3xgBVWzz" + + "QuNTAlY5aWAEN5CRqkYmOXDcge++gRlEry6ItrMEA0VZV0zsOFk2oDiT9W7slB3JefUOpWS4DMPJW6N0zRUDTtXaGmW" + + "rqt6W4vEGC0DnBI++A2ZkHoMmJ+qeCKBVkNJgAAADc4o2AAAAAA/w==", result.Result[0].Account.Data[0]); + Assert.AreEqual("base64", result.Result[0].Account.Data[1]); + Assert.AreEqual(false, result.Result[0].Account.Executable); + Assert.AreEqual(3486960UL, result.Result[0].Account.Lamports); + Assert.AreEqual("GrAkKfEpTKQuVHG2Y97Y2FF4i7y7Q5AHLK94JBy7Y5yv", result.Result[0].Account.Owner); + Assert.AreEqual(188UL, result.Result[0].Account.RentEpoch); + + FinishTest(messageHandlerMock, TestnetUri); + } + + + [TestMethod] + public void TestGetMultipleAccounts() + { + var responseData = File.ReadAllText("Resources/Http/Accounts/GetMultipleAccountsResponse.json"); + var requestData = File.ReadAllText("Resources/Http/Accounts/GetMultipleAccountsRequest.json"); + + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri, + }; + + var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); + + var result = sut.GetMultipleAccounts(new List { "Bbe9EKucmRtJr2J4dd5Eb5ybQmY7Fm7jYxKXxmmkLFsu", "9we6kjtbcZ2vy3GSLLsZTEhbAqXPTRvEyoxa8wxSqKp5" }); + Assert.AreEqual(requestData, sentMessage); + Assert.IsNotNull(result.Result); + Assert.IsTrue(result.WasSuccessful); + Assert.AreEqual(2, result.Result.Value.Count); + + Assert.AreEqual("base64", result.Result.Value[0].Data[1]); + Assert.AreEqual("", result.Result.Value[0].Data[0]); + Assert.AreEqual(false, result.Result.Value[0].Executable); + Assert.AreEqual(503668985208UL, result.Result.Value[0].Lamports); + Assert.AreEqual("11111111111111111111111111111111", result.Result.Value[0].Owner); + Assert.AreEqual(197UL, result.Result.Value[0].RentEpoch); + + FinishTest(messageHandlerMock, TestnetUri); + } + } +} \ No newline at end of file diff --git a/test/Solnet.Rpc.Test/SolanaRpcClientBlockTests.cs b/test/Solnet.Rpc.Test/SolanaRpcClientBlockTests.cs index 05a58175..fa84fede 100644 --- a/test/Solnet.Rpc.Test/SolanaRpcClientBlockTests.cs +++ b/test/Solnet.Rpc.Test/SolanaRpcClientBlockTests.cs @@ -1,508 +1,508 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Solnet.Rpc.Models; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net.Http; -using System.Text; -using System.Threading.Tasks; - -namespace Solnet.Rpc.Test -{ - public partial class SolanaRpcClientTest - { - [TestMethod] - public void TestGetBlock() - { - - var responseData = File.ReadAllText("Resources/Http/Blocks/GetBlockResponse.json"); - var requestData = File.ReadAllText("Resources/Http/Blocks/GetBlockRequest.json"); - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri - }; - var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); - - var res = sut.GetBlock(79662905); - - Assert.AreEqual(requestData, sentMessage); - Assert.IsNotNull(res.Result); - Assert.AreEqual(2, res.Result.Transactions.Length); - - Assert.AreEqual(66130135, res.Result.BlockHeight); - Assert.AreEqual(1622632900, res.Result.BlockTime); - Assert.AreEqual(79662904UL, res.Result.ParentSlot); - Assert.AreEqual("5wLhsKAH9SCPbRZc4qWf3GBiod9CD8sCEZfMiU25qW8", res.Result.Blockhash); - Assert.AreEqual("CjJ97j84mUq3o67CEqzEkTifXpHLBCD8GvmfBYLz4Zdg", res.Result.PreviousBlockhash); - - Assert.AreEqual(1, res.Result.Rewards.Length); - var rewards = res.Result.Rewards[0]; - - Assert.AreEqual(1785000, rewards.Lamports); - Assert.AreEqual(365762267923UL, rewards.PostBalance); - Assert.AreEqual("9zkU8suQBdhZVax2DSGNAnyEhEzfEELvA25CJhy5uwnW", rewards.Pubkey); - Assert.AreEqual(RewardType.Fee, rewards.RewardType); - - TransactionMetaInfo first = res.Result.Transactions[0]; - - Assert.IsNotNull(first.Meta.Error); - Assert.AreEqual(TransactionErrorType.InstructionError, first.Meta.Error.Type); - Assert.IsNotNull(first.Meta.Error.InstructionError); - Assert.AreEqual(InstructionErrorType.Custom, first.Meta.Error.InstructionError.Type); - Assert.AreEqual(0, first.Meta.Error.InstructionError.CustomError); - - Assert.AreEqual(5000UL, first.Meta.Fee); - Assert.AreEqual(0, first.Meta.InnerInstructions.Length); - Assert.AreEqual(2, first.Meta.LogMessages.Length); - Assert.AreEqual(5, first.Meta.PostBalances.Length); - Assert.AreEqual(35132731759UL, first.Meta.PostBalances[0]); - Assert.AreEqual(5, first.Meta.PreBalances.Length); - Assert.AreEqual(35132736759UL, first.Meta.PreBalances[0]); - Assert.AreEqual(0, first.Meta.PostTokenBalances.Length); - Assert.AreEqual(0, first.Meta.PreTokenBalances.Length); - - Assert.AreEqual(1, first.Transaction.Signatures.Length); - Assert.AreEqual("2Hh35eZPP1wZLYQ1HHv8PqGoRo73XirJeKFpBVc19msi6qeJHk3yUKqS1viRtqkdb545CerTWeywPFXxjKEhDWTK", first.Transaction.Signatures[0]); - - Assert.AreEqual(5, first.Transaction.Message.AccountKeys.Length); - Assert.AreEqual("DjuMPGThkGdyk2vDvDDYjTFSyxzTumdapnDNbvVZbYQE", first.Transaction.Message.AccountKeys[0]); - - Assert.AreEqual(0, first.Transaction.Message.Header.NumReadonlySignedAccounts); - Assert.AreEqual(3, first.Transaction.Message.Header.NumReadonlyUnsignedAccounts); - Assert.AreEqual(1, first.Transaction.Message.Header.NumRequiredSignatures); - - Assert.AreEqual(1, first.Transaction.Message.Instructions.Length); - Assert.AreEqual(4, first.Transaction.Message.Instructions[0].Accounts.Length); - Assert.AreEqual("2ZjTR1vUs2pHXyTLxtFDhN2tsm2HbaH36cAxzJcwaXf8y5jdTESsGNBLFaxGuWENxLa2ZL3cX9foNJcWbRq", first.Transaction.Message.Instructions[0].Data); - Assert.AreEqual(4, first.Transaction.Message.Instructions[0].ProgramIdIndex); - - Assert.AreEqual("D8qh6AeX4KaTe6ZBpsZDdntTQUyPy7x6Xjp7NnEigCWH", first.Transaction.Message.RecentBlockhash); - FinishTest(messageHandlerMock, TestnetUri); - } - - [TestMethod] - public void TestGetBlockConfirmed() - { - - var responseData = File.ReadAllText("Resources/Http/Blocks/GetBlockResponse.json"); - var requestData = File.ReadAllText("Resources/Http/Blocks/GetBlockRequest.json"); - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri - }; - var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); - - var res = sut.GetBlock(79662905, Types.Commitment.Confirmed); - - Assert.AreEqual(requestData, sentMessage); - Assert.IsNotNull(res.Result); - Assert.AreEqual(2, res.Result.Transactions.Length); - /// everything else was already validated above - FinishTest(messageHandlerMock, TestnetUri); - } - - [TestMethod] - public void TestGetBlockProductionNoArgs() - { - - var responseData = File.ReadAllText("Resources/Http/Blocks/GetBlockProductionNoArgsResponse.json"); - var requestData = File.ReadAllText("Resources/Http/Blocks/GetBlockProductionNoArgsRequest.json"); - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri - }; - var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); - - var res = sut.GetBlockProduction(); - - Assert.AreEqual(requestData, sentMessage); - - Assert.AreEqual(3, res.Result.Value.ByIdentity.Count); - Assert.AreEqual(79580256UL, res.Result.Value.Range.FirstSlot); - Assert.AreEqual(79712285UL, res.Result.Value.Range.LastSlot); - - Assert.IsTrue(res.Result.Value.ByIdentity.ContainsKey("121cur1YFVPZSoKQGNyjNr9sZZRa3eX2bSuYjXHtKD6")); - Assert.AreEqual(60, res.Result.Value.ByIdentity["121cur1YFVPZSoKQGNyjNr9sZZRa3eX2bSuYjXHtKD6"][0]); - - - FinishTest(messageHandlerMock, TestnetUri); - } - - [TestMethod] - public void TestGetBlockProductionIdentity() - { - - var responseData = File.ReadAllText("Resources/Http/Blocks/GetBlockProductionIdentityResponse.json"); - var requestData = File.ReadAllText("Resources/Http/Blocks/GetBlockProductionIdentityRequest.json"); - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri - }; - var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); - - var res = sut.GetBlockProduction("Bbe9EKucmRtJr2J4dd5Eb5ybQmY7Fm7jYxKXxmmkLFsu"); - - Assert.AreEqual(requestData, sentMessage); - - Assert.AreEqual(1, res.Result.Value.ByIdentity.Count); - Assert.AreEqual(79580256UL, res.Result.Value.Range.FirstSlot); - Assert.AreEqual(79712285UL, res.Result.Value.Range.LastSlot); - - Assert.IsTrue(res.Result.Value.ByIdentity.ContainsKey("Bbe9EKucmRtJr2J4dd5Eb5ybQmY7Fm7jYxKXxmmkLFsu")); - Assert.AreEqual(96, res.Result.Value.ByIdentity["Bbe9EKucmRtJr2J4dd5Eb5ybQmY7Fm7jYxKXxmmkLFsu"][0]); - - - FinishTest(messageHandlerMock, TestnetUri); - } - - [TestMethod] - public void TestGetBlockProductionRangeStart() - { - - var responseData = File.ReadAllText("Resources/Http/Blocks/GetBlockProductionRangeStartResponse.json"); - var requestData = File.ReadAllText("Resources/Http/Blocks/GetBlockProductionRangeStartRequest.json"); - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri - }; - var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); - - var res = sut.GetBlockProduction(79714135UL); - - Assert.AreEqual(requestData, sentMessage); - - Assert.AreEqual(35, res.Result.Value.ByIdentity.Count); - Assert.AreEqual(79714135UL, res.Result.Value.Range.FirstSlot); - Assert.AreEqual(79714275UL, res.Result.Value.Range.LastSlot); - - Assert.IsTrue(res.Result.Value.ByIdentity.ContainsKey("123vij84ecQEKUvQ7gYMKxKwKF6PbYSzCzzURYA4xULY")); - Assert.AreEqual(4, res.Result.Value.ByIdentity["123vij84ecQEKUvQ7gYMKxKwKF6PbYSzCzzURYA4xULY"][0]); - Assert.AreEqual(3, res.Result.Value.ByIdentity["123vij84ecQEKUvQ7gYMKxKwKF6PbYSzCzzURYA4xULY"][1]); - - - FinishTest(messageHandlerMock, TestnetUri); - } - - [TestMethod] - public void TestGetBlockProductionIdentityRange() - { - - var responseData = File.ReadAllText("Resources/Http/Blocks/GetBlockProductionIdentityRangeResponse.json"); - var requestData = File.ReadAllText("Resources/Http/Blocks/GetBlockProductionIdentityRangeRequest.json"); - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri - }; - var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); - - var res = sut.GetBlockProduction("Bbe9EKucmRtJr2J4dd5Eb5ybQmY7Fm7jYxKXxmmkLFsu", 79000000UL, 79500000UL); - - Assert.AreEqual(requestData, sentMessage); - - Assert.AreEqual(1, res.Result.Value.ByIdentity.Count); - Assert.AreEqual(79000000UL, res.Result.Value.Range.FirstSlot); - Assert.AreEqual(79500000UL, res.Result.Value.Range.LastSlot); - - Assert.IsTrue(res.Result.Value.ByIdentity.ContainsKey("Bbe9EKucmRtJr2J4dd5Eb5ybQmY7Fm7jYxKXxmmkLFsu")); - Assert.AreEqual(416, res.Result.Value.ByIdentity["Bbe9EKucmRtJr2J4dd5Eb5ybQmY7Fm7jYxKXxmmkLFsu"][0]); - Assert.AreEqual(341, res.Result.Value.ByIdentity["Bbe9EKucmRtJr2J4dd5Eb5ybQmY7Fm7jYxKXxmmkLFsu"][1]); - - - FinishTest(messageHandlerMock, TestnetUri); - } - - [TestMethod] - public void TestGetTransaction() - { - - var responseData = File.ReadAllText("Resources/Http/Blocks/GetTransactionResponse.json"); - var requestData = File.ReadAllText("Resources/Http/Blocks/GetTransactionRequest.json"); - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri - }; - var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); - - var res = sut.GetTransaction("5as3w4KMpY23MP5T1nkPVksjXjN7hnjHKqiDxRMxUNcw5XsCGtStayZib1kQdyR2D9w8dR11Ha9Xk38KP3kbAwM1"); - - Assert.AreEqual(requestData, sentMessage); - Assert.IsNotNull(res.Result); - Assert.AreEqual(79700345UL, res.Result.Slot); - - Assert.AreEqual(1622655364, res.Result.BlockTime); - - TransactionMetaInfo first = res.Result; - - Assert.IsNull(first.Meta.Error); - - Assert.AreEqual(5000UL, first.Meta.Fee); - Assert.AreEqual(0, first.Meta.InnerInstructions.Length); - Assert.AreEqual(2, first.Meta.LogMessages.Length); - Assert.AreEqual(5, first.Meta.PostBalances.Length); - Assert.AreEqual(395383573380UL, first.Meta.PostBalances[0]); - Assert.AreEqual(5, first.Meta.PreBalances.Length); - Assert.AreEqual(395383578380UL, first.Meta.PreBalances[0]); - Assert.AreEqual(0, first.Meta.PostTokenBalances.Length); - Assert.AreEqual(0, first.Meta.PreTokenBalances.Length); - - Assert.AreEqual(1, first.Transaction.Signatures.Length); - Assert.AreEqual("5as3w4KMpY23MP5T1nkPVksjXjN7hnjHKqiDxRMxUNcw5XsCGtStayZib1kQdyR2D9w8dR11Ha9Xk38KP3kbAwM1", first.Transaction.Signatures[0]); - - Assert.AreEqual(5, first.Transaction.Message.AccountKeys.Length); - Assert.AreEqual("EvVrzsxoj118sxxSTrcnc9u3fRdQfCc7d4gRzzX6TSqj", first.Transaction.Message.AccountKeys[0]); - - Assert.AreEqual(0, first.Transaction.Message.Header.NumReadonlySignedAccounts); - Assert.AreEqual(3, first.Transaction.Message.Header.NumReadonlyUnsignedAccounts); - Assert.AreEqual(1, first.Transaction.Message.Header.NumRequiredSignatures); - - Assert.AreEqual(1, first.Transaction.Message.Instructions.Length); - Assert.AreEqual(4, first.Transaction.Message.Instructions[0].Accounts.Length); - Assert.AreEqual("2kr3BYaDkghC7rvHsQYnBNoB4dhXrUmzgYMM4kbHSG7ALa3qsMPxfC9cJTFDKyJaC8VYSjrey9pvyRivtESUJrC3qzr89pvS2o6MQ" - + "hyRVxmh3raQStxFFYwZ6WyKFNoQXvcchBwy8uQGfhhUqzuLNREwRmZ5U2VgTjFWX8Vikqya6iyzvALQNZEvqz7ZoGEyRtJ6AzNyWbkUyEo63rZ5w3wnxmhr3Uood", - first.Transaction.Message.Instructions[0].Data); - - Assert.AreEqual(4, first.Transaction.Message.Instructions[0].ProgramIdIndex); - - Assert.AreEqual("6XGYfEJ5CGGBA5E8E7Gw4ToyDLDNNAyUCb7CJj1rLk21", first.Transaction.Message.RecentBlockhash); - FinishTest(messageHandlerMock, TestnetUri); - } - - [TestMethod] - public void TestGetBlocks() - { - - var responseData = File.ReadAllText("Resources/Http/Blocks/GetBlocksResponse.json"); - var requestData = File.ReadAllText("Resources/Http/Blocks/GetBlocksRequest.json"); - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri - }; - var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); - - var res = sut.GetBlocks(79_499_950, 79_500_000); - - Assert.AreEqual(requestData, sentMessage); - Assert.IsNotNull(res.Result); - Assert.AreEqual(39, res.Result.Count); - Assert.AreEqual(79499950UL, res.Result[0]); - Assert.AreEqual(79500000UL, res.Result[38]); - - FinishTest(messageHandlerMock, TestnetUri); - } - - [TestMethod] - public void TestGetBlocksWithLimit() - { - - var responseData = File.ReadAllText("Resources/Http/Blocks/GetBlocksWithLimitResponse.json"); - var requestData = File.ReadAllText("Resources/Http/Blocks/GetBlocksWithLimitRequest.json"); - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri - }; - var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); - - var res = sut.GetBlocksWithLimit(79_699_950, 2); - - Assert.AreEqual(requestData, sentMessage); - Assert.IsNotNull(res.Result); - Assert.AreEqual(2, res.Result.Count); - Assert.AreEqual(79699950UL, res.Result[0]); - Assert.AreEqual(79699951UL, res.Result[1]); - - FinishTest(messageHandlerMock, TestnetUri); - } - - [TestMethod] - public void TestGetFirstAvailableBlock() - { - - var responseData = File.ReadAllText("Resources/Http/Blocks/GetFirstAvailableBlockResponse.json"); - var requestData = File.ReadAllText("Resources/Http/Blocks/GetFirstAvailableBlockRequest.json"); - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri - }; - var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); - - var res = sut.GetFirstAvailableBlock(); - - Assert.AreEqual(requestData, sentMessage); - Assert.IsNotNull(res.Result); - Assert.AreEqual(39368303UL, res.Result); - - FinishTest(messageHandlerMock, TestnetUri); - } - - [TestMethod] - public void TestGetBlockHeight() - { - var responseData = File.ReadAllText("Resources/Http/Blocks/GetBlockHeightResponse.json"); - var requestData = File.ReadAllText("Resources/Http/Blocks/GetBlockHeightRequest.json"); - - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri, - }; - - var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); - - var result = sut.GetBlockHeight(); - Assert.AreEqual(requestData, sentMessage); - Assert.IsNotNull(result); - Assert.IsTrue(result.WasSuccessful); - Assert.AreEqual(1233UL, result.Result); - - FinishTest(messageHandlerMock, TestnetUri); - } - - [TestMethod] - public void TestGetBlockCommitment() - { - var responseData = File.ReadAllText("Resources/Http/Blocks/GetBlockCommitmentResponse.json"); - var requestData = File.ReadAllText("Resources/Http/Blocks/GetBlockCommitmentRequest.json"); - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri, - }; - - var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); - var result = sut.GetBlockCommitment(78561320); - - Assert.AreEqual(requestData, sentMessage); - Assert.IsNotNull(result.Result); - Assert.IsTrue(result.WasSuccessful); - Assert.AreEqual(null, result.Result.Commitment); - Assert.AreEqual(78380558524696194UL, result.Result.TotalStake); - - FinishTest(messageHandlerMock, TestnetUri); - } - - [TestMethod] - public void TestGetBlockTime() - { - var responseData = File.ReadAllText("Resources/Http/Blocks/GetBlockTimeResponse.json"); - var requestData = File.ReadAllText("Resources/Http/Blocks/GetBlockTimeRequest.json"); - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri, - }; - - var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); - var result = sut.GetBlockTime(78561320); - - Assert.AreEqual(requestData, sentMessage); - Assert.IsNotNull(result.Result); - Assert.IsTrue(result.WasSuccessful); - Assert.AreEqual(1621971949UL, result.Result); - - FinishTest(messageHandlerMock, TestnetUri); - } - - [TestMethod] - public void TestGetFeeCalculatorForBlockhash() - { - var responseData = File.ReadAllText("Resources/Http/GetFeeCalculatorForBlockhashResponse.json"); - var requestData = File.ReadAllText("Resources/Http/GetFeeCalculatorForBlockhashRequest.json"); - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri, - }; - - var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); - var result = sut.GetFeeCalculatorForBlockhash("GJxqhuxcgfn5Tcj6y3f8X4FeCDd2RQ6SnEMo1AAxrPRZ"); - - Assert.AreEqual(requestData, sentMessage); - Assert.IsNotNull(result.Result); - Assert.IsTrue(result.WasSuccessful); - Assert.AreEqual(221UL, result.Result.Context.Slot); - Assert.AreEqual(5000UL, result.Result.Value.FeeCalculator.LamportsPerSignature); - - FinishTest(messageHandlerMock, TestnetUri); - } - - [TestMethod] - public void TestGetRecentBlockHash() - { - var responseData = File.ReadAllText("Resources/Http/GetRecentBlockhashResponse.json"); - var requestData = File.ReadAllText("Resources/Http/GetRecentBlockhashRequest.json"); - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri, - }; - - var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); - var result = sut.GetRecentBlockHash(); - - Assert.AreEqual(requestData, sentMessage); - Assert.IsNotNull(result.Result); - Assert.IsTrue(result.WasSuccessful); - Assert.AreEqual(79206433UL, result.Result.Context.Slot); - Assert.AreEqual("FJGZeJiYkwCZCnFbGujHxfVGnFgrgZiomczHr247Tn2p", result.Result.Value.Blockhash); - Assert.AreEqual(5000UL, result.Result.Value.FeeCalculator.LamportsPerSignature); - - FinishTest(messageHandlerMock, TestnetUri); - } - } -} +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Solnet.Rpc.Models; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; + +namespace Solnet.Rpc.Test +{ + public partial class SolanaRpcClientTest + { + [TestMethod] + public void TestGetBlock() + { + + var responseData = File.ReadAllText("Resources/Http/Blocks/GetBlockResponse.json"); + var requestData = File.ReadAllText("Resources/Http/Blocks/GetBlockRequest.json"); + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri + }; + var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); + + var res = sut.GetBlock(79662905); + + Assert.AreEqual(requestData, sentMessage); + Assert.IsNotNull(res.Result); + Assert.AreEqual(2, res.Result.Transactions.Length); + + Assert.AreEqual(66130135, res.Result.BlockHeight); + Assert.AreEqual(1622632900, res.Result.BlockTime); + Assert.AreEqual(79662904UL, res.Result.ParentSlot); + Assert.AreEqual("5wLhsKAH9SCPbRZc4qWf3GBiod9CD8sCEZfMiU25qW8", res.Result.Blockhash); + Assert.AreEqual("CjJ97j84mUq3o67CEqzEkTifXpHLBCD8GvmfBYLz4Zdg", res.Result.PreviousBlockhash); + + Assert.AreEqual(1, res.Result.Rewards.Length); + var rewards = res.Result.Rewards[0]; + + Assert.AreEqual(1785000, rewards.Lamports); + Assert.AreEqual(365762267923UL, rewards.PostBalance); + Assert.AreEqual("9zkU8suQBdhZVax2DSGNAnyEhEzfEELvA25CJhy5uwnW", rewards.Pubkey); + Assert.AreEqual(RewardType.Fee, rewards.RewardType); + + TransactionMetaInfo first = res.Result.Transactions[0]; + + Assert.IsNotNull(first.Meta.Error); + Assert.AreEqual(TransactionErrorType.InstructionError, first.Meta.Error.Type); + Assert.IsNotNull(first.Meta.Error.InstructionError); + Assert.AreEqual(InstructionErrorType.Custom, first.Meta.Error.InstructionError.Type); + Assert.AreEqual(0, first.Meta.Error.InstructionError.CustomError); + + Assert.AreEqual(5000UL, first.Meta.Fee); + Assert.AreEqual(0, first.Meta.InnerInstructions.Length); + Assert.AreEqual(2, first.Meta.LogMessages.Length); + Assert.AreEqual(5, first.Meta.PostBalances.Length); + Assert.AreEqual(35132731759UL, first.Meta.PostBalances[0]); + Assert.AreEqual(5, first.Meta.PreBalances.Length); + Assert.AreEqual(35132736759UL, first.Meta.PreBalances[0]); + Assert.AreEqual(0, first.Meta.PostTokenBalances.Length); + Assert.AreEqual(0, first.Meta.PreTokenBalances.Length); + + Assert.AreEqual(1, first.Transaction.Signatures.Length); + Assert.AreEqual("2Hh35eZPP1wZLYQ1HHv8PqGoRo73XirJeKFpBVc19msi6qeJHk3yUKqS1viRtqkdb545CerTWeywPFXxjKEhDWTK", first.Transaction.Signatures[0]); + + Assert.AreEqual(5, first.Transaction.Message.AccountKeys.Length); + Assert.AreEqual("DjuMPGThkGdyk2vDvDDYjTFSyxzTumdapnDNbvVZbYQE", first.Transaction.Message.AccountKeys[0]); + + Assert.AreEqual(0, first.Transaction.Message.Header.NumReadonlySignedAccounts); + Assert.AreEqual(3, first.Transaction.Message.Header.NumReadonlyUnsignedAccounts); + Assert.AreEqual(1, first.Transaction.Message.Header.NumRequiredSignatures); + + Assert.AreEqual(1, first.Transaction.Message.Instructions.Length); + Assert.AreEqual(4, first.Transaction.Message.Instructions[0].Accounts.Length); + Assert.AreEqual("2ZjTR1vUs2pHXyTLxtFDhN2tsm2HbaH36cAxzJcwaXf8y5jdTESsGNBLFaxGuWENxLa2ZL3cX9foNJcWbRq", first.Transaction.Message.Instructions[0].Data); + Assert.AreEqual(4, first.Transaction.Message.Instructions[0].ProgramIdIndex); + + Assert.AreEqual("D8qh6AeX4KaTe6ZBpsZDdntTQUyPy7x6Xjp7NnEigCWH", first.Transaction.Message.RecentBlockhash); + FinishTest(messageHandlerMock, TestnetUri); + } + + [TestMethod] + public void TestGetBlockConfirmed() + { + + var responseData = File.ReadAllText("Resources/Http/Blocks/GetBlockResponse.json"); + var requestData = File.ReadAllText("Resources/Http/Blocks/GetBlockRequest.json"); + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri + }; + var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); + + var res = sut.GetBlock(79662905, Types.Commitment.Confirmed); + + Assert.AreEqual(requestData, sentMessage); + Assert.IsNotNull(res.Result); + Assert.AreEqual(2, res.Result.Transactions.Length); + /// everything else was already validated above + FinishTest(messageHandlerMock, TestnetUri); + } + + [TestMethod] + public void TestGetBlockProductionNoArgs() + { + + var responseData = File.ReadAllText("Resources/Http/Blocks/GetBlockProductionNoArgsResponse.json"); + var requestData = File.ReadAllText("Resources/Http/Blocks/GetBlockProductionNoArgsRequest.json"); + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri + }; + var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); + + var res = sut.GetBlockProduction(); + + Assert.AreEqual(requestData, sentMessage); + + Assert.AreEqual(3, res.Result.Value.ByIdentity.Count); + Assert.AreEqual(79580256UL, res.Result.Value.Range.FirstSlot); + Assert.AreEqual(79712285UL, res.Result.Value.Range.LastSlot); + + Assert.IsTrue(res.Result.Value.ByIdentity.ContainsKey("121cur1YFVPZSoKQGNyjNr9sZZRa3eX2bSuYjXHtKD6")); + Assert.AreEqual(60, res.Result.Value.ByIdentity["121cur1YFVPZSoKQGNyjNr9sZZRa3eX2bSuYjXHtKD6"][0]); + + + FinishTest(messageHandlerMock, TestnetUri); + } + + [TestMethod] + public void TestGetBlockProductionIdentity() + { + + var responseData = File.ReadAllText("Resources/Http/Blocks/GetBlockProductionIdentityResponse.json"); + var requestData = File.ReadAllText("Resources/Http/Blocks/GetBlockProductionIdentityRequest.json"); + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri + }; + var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); + + var res = sut.GetBlockProduction("Bbe9EKucmRtJr2J4dd5Eb5ybQmY7Fm7jYxKXxmmkLFsu"); + + Assert.AreEqual(requestData, sentMessage); + + Assert.AreEqual(1, res.Result.Value.ByIdentity.Count); + Assert.AreEqual(79580256UL, res.Result.Value.Range.FirstSlot); + Assert.AreEqual(79712285UL, res.Result.Value.Range.LastSlot); + + Assert.IsTrue(res.Result.Value.ByIdentity.ContainsKey("Bbe9EKucmRtJr2J4dd5Eb5ybQmY7Fm7jYxKXxmmkLFsu")); + Assert.AreEqual(96, res.Result.Value.ByIdentity["Bbe9EKucmRtJr2J4dd5Eb5ybQmY7Fm7jYxKXxmmkLFsu"][0]); + + + FinishTest(messageHandlerMock, TestnetUri); + } + + [TestMethod] + public void TestGetBlockProductionRangeStart() + { + + var responseData = File.ReadAllText("Resources/Http/Blocks/GetBlockProductionRangeStartResponse.json"); + var requestData = File.ReadAllText("Resources/Http/Blocks/GetBlockProductionRangeStartRequest.json"); + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri + }; + var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); + + var res = sut.GetBlockProduction(79714135UL); + + Assert.AreEqual(requestData, sentMessage); + + Assert.AreEqual(35, res.Result.Value.ByIdentity.Count); + Assert.AreEqual(79714135UL, res.Result.Value.Range.FirstSlot); + Assert.AreEqual(79714275UL, res.Result.Value.Range.LastSlot); + + Assert.IsTrue(res.Result.Value.ByIdentity.ContainsKey("123vij84ecQEKUvQ7gYMKxKwKF6PbYSzCzzURYA4xULY")); + Assert.AreEqual(4, res.Result.Value.ByIdentity["123vij84ecQEKUvQ7gYMKxKwKF6PbYSzCzzURYA4xULY"][0]); + Assert.AreEqual(3, res.Result.Value.ByIdentity["123vij84ecQEKUvQ7gYMKxKwKF6PbYSzCzzURYA4xULY"][1]); + + + FinishTest(messageHandlerMock, TestnetUri); + } + + [TestMethod] + public void TestGetBlockProductionIdentityRange() + { + + var responseData = File.ReadAllText("Resources/Http/Blocks/GetBlockProductionIdentityRangeResponse.json"); + var requestData = File.ReadAllText("Resources/Http/Blocks/GetBlockProductionIdentityRangeRequest.json"); + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri + }; + var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); + + var res = sut.GetBlockProduction("Bbe9EKucmRtJr2J4dd5Eb5ybQmY7Fm7jYxKXxmmkLFsu", 79000000UL, 79500000UL); + + Assert.AreEqual(requestData, sentMessage); + + Assert.AreEqual(1, res.Result.Value.ByIdentity.Count); + Assert.AreEqual(79000000UL, res.Result.Value.Range.FirstSlot); + Assert.AreEqual(79500000UL, res.Result.Value.Range.LastSlot); + + Assert.IsTrue(res.Result.Value.ByIdentity.ContainsKey("Bbe9EKucmRtJr2J4dd5Eb5ybQmY7Fm7jYxKXxmmkLFsu")); + Assert.AreEqual(416, res.Result.Value.ByIdentity["Bbe9EKucmRtJr2J4dd5Eb5ybQmY7Fm7jYxKXxmmkLFsu"][0]); + Assert.AreEqual(341, res.Result.Value.ByIdentity["Bbe9EKucmRtJr2J4dd5Eb5ybQmY7Fm7jYxKXxmmkLFsu"][1]); + + + FinishTest(messageHandlerMock, TestnetUri); + } + + [TestMethod] + public void TestGetTransaction() + { + + var responseData = File.ReadAllText("Resources/Http/Blocks/GetTransactionResponse.json"); + var requestData = File.ReadAllText("Resources/Http/Blocks/GetTransactionRequest.json"); + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri + }; + var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); + + var res = sut.GetTransaction("5as3w4KMpY23MP5T1nkPVksjXjN7hnjHKqiDxRMxUNcw5XsCGtStayZib1kQdyR2D9w8dR11Ha9Xk38KP3kbAwM1"); + + Assert.AreEqual(requestData, sentMessage); + Assert.IsNotNull(res.Result); + Assert.AreEqual(79700345UL, res.Result.Slot); + + Assert.AreEqual(1622655364, res.Result.BlockTime); + + TransactionMetaInfo first = res.Result; + + Assert.IsNull(first.Meta.Error); + + Assert.AreEqual(5000UL, first.Meta.Fee); + Assert.AreEqual(0, first.Meta.InnerInstructions.Length); + Assert.AreEqual(2, first.Meta.LogMessages.Length); + Assert.AreEqual(5, first.Meta.PostBalances.Length); + Assert.AreEqual(395383573380UL, first.Meta.PostBalances[0]); + Assert.AreEqual(5, first.Meta.PreBalances.Length); + Assert.AreEqual(395383578380UL, first.Meta.PreBalances[0]); + Assert.AreEqual(0, first.Meta.PostTokenBalances.Length); + Assert.AreEqual(0, first.Meta.PreTokenBalances.Length); + + Assert.AreEqual(1, first.Transaction.Signatures.Length); + Assert.AreEqual("5as3w4KMpY23MP5T1nkPVksjXjN7hnjHKqiDxRMxUNcw5XsCGtStayZib1kQdyR2D9w8dR11Ha9Xk38KP3kbAwM1", first.Transaction.Signatures[0]); + + Assert.AreEqual(5, first.Transaction.Message.AccountKeys.Length); + Assert.AreEqual("EvVrzsxoj118sxxSTrcnc9u3fRdQfCc7d4gRzzX6TSqj", first.Transaction.Message.AccountKeys[0]); + + Assert.AreEqual(0, first.Transaction.Message.Header.NumReadonlySignedAccounts); + Assert.AreEqual(3, first.Transaction.Message.Header.NumReadonlyUnsignedAccounts); + Assert.AreEqual(1, first.Transaction.Message.Header.NumRequiredSignatures); + + Assert.AreEqual(1, first.Transaction.Message.Instructions.Length); + Assert.AreEqual(4, first.Transaction.Message.Instructions[0].Accounts.Length); + Assert.AreEqual("2kr3BYaDkghC7rvHsQYnBNoB4dhXrUmzgYMM4kbHSG7ALa3qsMPxfC9cJTFDKyJaC8VYSjrey9pvyRivtESUJrC3qzr89pvS2o6MQ" + + "hyRVxmh3raQStxFFYwZ6WyKFNoQXvcchBwy8uQGfhhUqzuLNREwRmZ5U2VgTjFWX8Vikqya6iyzvALQNZEvqz7ZoGEyRtJ6AzNyWbkUyEo63rZ5w3wnxmhr3Uood", + first.Transaction.Message.Instructions[0].Data); + + Assert.AreEqual(4, first.Transaction.Message.Instructions[0].ProgramIdIndex); + + Assert.AreEqual("6XGYfEJ5CGGBA5E8E7Gw4ToyDLDNNAyUCb7CJj1rLk21", first.Transaction.Message.RecentBlockhash); + FinishTest(messageHandlerMock, TestnetUri); + } + + [TestMethod] + public void TestGetBlocks() + { + + var responseData = File.ReadAllText("Resources/Http/Blocks/GetBlocksResponse.json"); + var requestData = File.ReadAllText("Resources/Http/Blocks/GetBlocksRequest.json"); + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri + }; + var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); + + var res = sut.GetBlocks(79_499_950, 79_500_000); + + Assert.AreEqual(requestData, sentMessage); + Assert.IsNotNull(res.Result); + Assert.AreEqual(39, res.Result.Count); + Assert.AreEqual(79499950UL, res.Result[0]); + Assert.AreEqual(79500000UL, res.Result[38]); + + FinishTest(messageHandlerMock, TestnetUri); + } + + [TestMethod] + public void TestGetBlocksWithLimit() + { + + var responseData = File.ReadAllText("Resources/Http/Blocks/GetBlocksWithLimitResponse.json"); + var requestData = File.ReadAllText("Resources/Http/Blocks/GetBlocksWithLimitRequest.json"); + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri + }; + var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); + + var res = sut.GetBlocksWithLimit(79_699_950, 2); + + Assert.AreEqual(requestData, sentMessage); + Assert.IsNotNull(res.Result); + Assert.AreEqual(2, res.Result.Count); + Assert.AreEqual(79699950UL, res.Result[0]); + Assert.AreEqual(79699951UL, res.Result[1]); + + FinishTest(messageHandlerMock, TestnetUri); + } + + [TestMethod] + public void TestGetFirstAvailableBlock() + { + + var responseData = File.ReadAllText("Resources/Http/Blocks/GetFirstAvailableBlockResponse.json"); + var requestData = File.ReadAllText("Resources/Http/Blocks/GetFirstAvailableBlockRequest.json"); + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri + }; + var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); + + var res = sut.GetFirstAvailableBlock(); + + Assert.AreEqual(requestData, sentMessage); + Assert.IsNotNull(res.Result); + Assert.AreEqual(39368303UL, res.Result); + + FinishTest(messageHandlerMock, TestnetUri); + } + + [TestMethod] + public void TestGetBlockHeight() + { + var responseData = File.ReadAllText("Resources/Http/Blocks/GetBlockHeightResponse.json"); + var requestData = File.ReadAllText("Resources/Http/Blocks/GetBlockHeightRequest.json"); + + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri, + }; + + var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); + + var result = sut.GetBlockHeight(); + Assert.AreEqual(requestData, sentMessage); + Assert.IsNotNull(result); + Assert.IsTrue(result.WasSuccessful); + Assert.AreEqual(1233UL, result.Result); + + FinishTest(messageHandlerMock, TestnetUri); + } + + [TestMethod] + public void TestGetBlockCommitment() + { + var responseData = File.ReadAllText("Resources/Http/Blocks/GetBlockCommitmentResponse.json"); + var requestData = File.ReadAllText("Resources/Http/Blocks/GetBlockCommitmentRequest.json"); + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri, + }; + + var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); + var result = sut.GetBlockCommitment(78561320); + + Assert.AreEqual(requestData, sentMessage); + Assert.IsNotNull(result.Result); + Assert.IsTrue(result.WasSuccessful); + Assert.AreEqual(null, result.Result.Commitment); + Assert.AreEqual(78380558524696194UL, result.Result.TotalStake); + + FinishTest(messageHandlerMock, TestnetUri); + } + + [TestMethod] + public void TestGetBlockTime() + { + var responseData = File.ReadAllText("Resources/Http/Blocks/GetBlockTimeResponse.json"); + var requestData = File.ReadAllText("Resources/Http/Blocks/GetBlockTimeRequest.json"); + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri, + }; + + var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); + var result = sut.GetBlockTime(78561320); + + Assert.AreEqual(requestData, sentMessage); + Assert.IsNotNull(result.Result); + Assert.IsTrue(result.WasSuccessful); + Assert.AreEqual(1621971949UL, result.Result); + + FinishTest(messageHandlerMock, TestnetUri); + } + + [TestMethod] + public void TestGetFeeCalculatorForBlockhash() + { + var responseData = File.ReadAllText("Resources/Http/GetFeeCalculatorForBlockhashResponse.json"); + var requestData = File.ReadAllText("Resources/Http/GetFeeCalculatorForBlockhashRequest.json"); + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri, + }; + + var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); + var result = sut.GetFeeCalculatorForBlockhash("GJxqhuxcgfn5Tcj6y3f8X4FeCDd2RQ6SnEMo1AAxrPRZ"); + + Assert.AreEqual(requestData, sentMessage); + Assert.IsNotNull(result.Result); + Assert.IsTrue(result.WasSuccessful); + Assert.AreEqual(221UL, result.Result.Context.Slot); + Assert.AreEqual(5000UL, result.Result.Value.FeeCalculator.LamportsPerSignature); + + FinishTest(messageHandlerMock, TestnetUri); + } + + [TestMethod] + public void TestGetRecentBlockHash() + { + var responseData = File.ReadAllText("Resources/Http/GetRecentBlockhashResponse.json"); + var requestData = File.ReadAllText("Resources/Http/GetRecentBlockhashRequest.json"); + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri, + }; + + var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); + var result = sut.GetRecentBlockHash(); + + Assert.AreEqual(requestData, sentMessage); + Assert.IsNotNull(result.Result); + Assert.IsTrue(result.WasSuccessful); + Assert.AreEqual(79206433UL, result.Result.Context.Slot); + Assert.AreEqual("FJGZeJiYkwCZCnFbGujHxfVGnFgrgZiomczHr247Tn2p", result.Result.Value.Blockhash); + Assert.AreEqual(5000UL, result.Result.Value.FeeCalculator.LamportsPerSignature); + + FinishTest(messageHandlerMock, TestnetUri); + } + } +} \ No newline at end of file diff --git a/test/Solnet.Rpc.Test/SolanaRpcClientTest.cs b/test/Solnet.Rpc.Test/SolanaRpcClientTest.cs index 6cc6ceda..fff039a9 100644 --- a/test/Solnet.Rpc.Test/SolanaRpcClientTest.cs +++ b/test/Solnet.Rpc.Test/SolanaRpcClientTest.cs @@ -1,1572 +1,1572 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; -using Moq.Protected; -using Solnet.Rpc.Models; -using System; -using System.Collections.Generic; -using System.IO; -using System.Net; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; - -namespace Solnet.Rpc.Test -{ - [TestClass] - public partial class SolanaRpcClientTest - { - private const string TestnetUrl = "https://testnet.solana.com"; - private static readonly Uri TestnetUri = new Uri(TestnetUrl); - - /// - /// Setup the test with the request and response data. - /// - /// Capture the sent content. - /// The response content. - private Mock SetupTest(Action sentPayloadCapture, string responseContent) - { - var messageHandlerMock = new Mock(MockBehavior.Strict); - messageHandlerMock - .Protected() - .Setup>( - "SendAsync", - ItExpr.Is( - message => message.Method == HttpMethod.Post && - message.RequestUri == TestnetUri), - ItExpr.IsAny() - ) - .Callback((httpRequest, ct) => - sentPayloadCapture(httpRequest.Content.ReadAsStringAsync(ct).Result)) - .ReturnsAsync(new HttpResponseMessage - { - StatusCode = HttpStatusCode.OK, - Content = new StringContent(responseContent), - }) - .Verifiable(); - return messageHandlerMock; - } - - /// - /// Finish the test by asserting the http request went as expected. - /// - /// The request uri. - private void FinishTest(Mock mockHandler, Uri expectedUri) - { - mockHandler.Protected().Verify( - "SendAsync", - Times.Exactly(1), - ItExpr.Is(req => - req.Method == HttpMethod.Post - && req.RequestUri == expectedUri - ), - ItExpr.IsAny() - ); - } - - [TestMethod] - public void TestEmptyPayloadRequest() - { - var responseData = File.ReadAllText("Resources/Http/EmptyPayloadResponse.json"); - var requestData = File.ReadAllText("Resources/Http/EmptyPayloadRequest.json"); - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri, - }; - - var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); - - var result = sut.GetBalance(""); - - Assert.AreEqual(requestData, sentMessage); - Assert.IsNotNull(result); - Assert.IsNull(result.Result); - Assert.IsTrue(result.WasHttpRequestSuccessful); - Assert.IsFalse(result.WasRequestSuccessfullyHandled); - Assert.IsFalse(result.WasSuccessful); - Assert.AreEqual("Invalid param: WrongSize", result.Reason); - Assert.AreEqual(-32602, result.ServerErrorCode); - } - - [TestMethod] - public void TestBadAddressExceptionRequest() - { - var msg = "something bad happenned"; - var responseData = File.ReadAllText("Resources/Http/EmptyPayloadResponse.json"); - var requestData = File.ReadAllText("Resources/Http/EmptyPayloadRequest.json"); - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - messageHandlerMock.Protected().Setup( - "SendAsync", - ItExpr.IsAny(), - ItExpr.IsAny() - ).Throws(new HttpRequestException(msg)); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri, - }; - - var sut = new SolanaRpcClient("https://non.existing.adddress.com", null, httpClient); - - var result = sut.GetBalance(""); - Assert.AreEqual(HttpStatusCode.BadRequest, result.HttpStatusCode); - Assert.AreEqual(msg, result.Reason); - Assert.IsFalse(result.WasHttpRequestSuccessful); - Assert.IsFalse(result.WasRequestSuccessfullyHandled); - } - - [TestMethod] - public void TestBadAddress2ExceptionRequest() - { - var msg = "not found bro"; - var responseData = File.ReadAllText("Resources/Http/EmptyPayloadResponse.json"); - var requestData = File.ReadAllText("Resources/Http/EmptyPayloadRequest.json"); - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - messageHandlerMock.Protected().Setup( - "SendAsync", - ItExpr.IsAny(), - ItExpr.IsAny() - ).Throws(new HttpRequestException(msg, null, HttpStatusCode.NotFound)); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri, - }; - - var sut = new SolanaRpcClient("https://valid.server.but.invalid.endpoint.com", null, httpClient); - - var result = sut.GetBalance(""); - Assert.AreEqual(HttpStatusCode.NotFound, result.HttpStatusCode); - Assert.AreEqual(msg, result.Reason); - Assert.IsFalse(result.WasHttpRequestSuccessful); - Assert.IsFalse(result.WasRequestSuccessfullyHandled); - } - - [TestMethod] - public void TestGetBalance() - { - var responseData = File.ReadAllText("Resources/Http/GetBalanceResponse.json"); - var requestData = File.ReadAllText("Resources/Http/GetBalanceRequest.json"); - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri, - }; - - var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); - var result = sut.GetBalance("hoakwpFB8UoLnPpLC56gsjpY7XbVwaCuRQRMQzN5TVh"); - - Assert.AreEqual(requestData, sentMessage); - Assert.IsNotNull(result.Result); - Assert.IsTrue(result.WasSuccessful); - Assert.AreEqual(79274779UL, result.Result.Context.Slot); - Assert.AreEqual(168855000000UL, result.Result.Value); - - FinishTest(messageHandlerMock, TestnetUri); - } - - [TestMethod] - public void TestGetBalanceConfirmed() - { - var responseData = File.ReadAllText("Resources/Http/GetBalanceResponse.json"); - var requestData = File.ReadAllText("Resources/Http/GetBalanceConfirmedRequest.json"); - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri, - }; - - var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); - var result = sut.GetBalance("hoakwpFB8UoLnPpLC56gsjpY7XbVwaCuRQRMQzN5TVh", Types.Commitment.Confirmed); - - Assert.AreEqual(requestData, sentMessage); - Assert.IsNotNull(result.Result); - Assert.IsTrue(result.WasSuccessful); - Assert.AreEqual(79274779UL, result.Result.Context.Slot); - Assert.AreEqual(168855000000UL, result.Result.Value); - - FinishTest(messageHandlerMock, TestnetUri); - } - - [TestMethod] - public void TestGetClusterNodes() - { - var responseData = File.ReadAllText("Resources/Http/GetClusterNodesResponse.json"); - var requestData = File.ReadAllText("Resources/Http/GetClusterNodesRequest.json"); - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri, - }; - - var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); - var result = sut.GetClusterNodes(); - - Assert.AreEqual(requestData, sentMessage); - Assert.IsNotNull(result.Result); - Assert.IsTrue(result.WasSuccessful); - Assert.AreEqual(5, result.Result.Count); - Assert.AreEqual(3533521759UL, result.Result[0].FeatureSet); - Assert.AreEqual("216.24.140.155:8001", result.Result[0].Gossip); - Assert.AreEqual("5D1fNXzvv5NjV1ysLjirC4WY92RNsVH18vjmcszZd8on", result.Result[0].PublicKey); - Assert.AreEqual("216.24.140.155:8899", result.Result[0].Rpc); - Assert.AreEqual(18122UL, result.Result[0].ShredVersion); - Assert.AreEqual("216.24.140.155:8004", result.Result[0].Tpu); - Assert.AreEqual("1.7.0", result.Result[0].Version); - - FinishTest(messageHandlerMock, TestnetUri); - } - - [TestMethod] - public void TestGetEpochInfo() - { - var responseData = File.ReadAllText("Resources/Http/GetEpochInfoResponse.json"); - var requestData = File.ReadAllText("Resources/Http/GetEpochInfoRequest.json"); - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri, - }; - - var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); - var result = sut.GetEpochInfo(); - - Assert.AreEqual(requestData, sentMessage); - Assert.IsNotNull(result.Result); - Assert.IsTrue(result.WasSuccessful); - Assert.AreEqual(166598UL, result.Result.AbsoluteSlot); - Assert.AreEqual(166500UL, result.Result.BlockHeight); - Assert.AreEqual(27UL, result.Result.Epoch); - Assert.AreEqual(2790UL, result.Result.SlotIndex); - Assert.AreEqual(8192UL, result.Result.SlotsInEpoch); - - FinishTest(messageHandlerMock, TestnetUri); - } - - [TestMethod] - public void TestGetEpochSchedule() - { - var responseData = File.ReadAllText("Resources/Http/GetEpochScheduleResponse.json"); - var requestData = File.ReadAllText("Resources/Http/GetEpochScheduleRequest.json"); - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri, - }; - - var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); - var result = sut.GetEpochSchedule(); - - Assert.AreEqual(requestData, sentMessage); - Assert.IsNotNull(result.Result); - Assert.IsTrue(result.WasSuccessful); - Assert.AreEqual(8UL, result.Result.FirstNormalEpoch); - Assert.AreEqual(8160UL, result.Result.FirstNormalSlot); - Assert.AreEqual(8192UL, result.Result.LeaderScheduleSlotOffset); - Assert.AreEqual(8192UL, result.Result.SlotsPerEpoch); - Assert.AreEqual(true, result.Result.Warmup); - - FinishTest(messageHandlerMock, TestnetUri); - } - - [TestMethod] - public void TestGetFeeRateGovernor() - { - var responseData = File.ReadAllText("Resources/Http/GetFeeRateGovernorResponse.json"); - var requestData = File.ReadAllText("Resources/Http/GetFeeRateGovernorRequest.json"); - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri, - }; - - var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); - var result = sut.GetFeeRateGovernor(); - - Assert.AreEqual(requestData, sentMessage); - Assert.IsNotNull(result.Result); - Assert.IsTrue(result.WasSuccessful); - Assert.AreEqual(54UL, result.Result.Context.Slot); - Assert.AreEqual(50, result.Result.Value.FeeRateGovernor.BurnPercent); - Assert.AreEqual(100000UL, result.Result.Value.FeeRateGovernor.MaxLamportsPerSignature); - Assert.AreEqual(5000UL, result.Result.Value.FeeRateGovernor.MinLamportsPerSignature); - Assert.AreEqual(10000UL, result.Result.Value.FeeRateGovernor.TargetLamportsPerSignature); - Assert.AreEqual(20000UL, result.Result.Value.FeeRateGovernor.TargetSignaturesPerSlot); - - FinishTest(messageHandlerMock, TestnetUri); - } - - [TestMethod] - public void TestGetFees() - { - var responseData = File.ReadAllText("Resources/Http/GetFeesResponse.json"); - var requestData = File.ReadAllText("Resources/Http/GetFeesRequest.json"); - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri, - }; - - var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); - var result = sut.GetFees(); - - Assert.AreEqual(requestData, sentMessage); - Assert.IsNotNull(result.Result); - Assert.IsTrue(result.WasSuccessful); - Assert.AreEqual(1UL, result.Result.Context.Slot); - Assert.AreEqual("CSymwgTNX1j3E4qhKfJAUE41nBWEwXufoYryPbkde5RR", result.Result.Value.Blockhash); - Assert.AreEqual(5000UL, result.Result.Value.FeeCalculator.LamportsPerSignature); - Assert.AreEqual(297UL, result.Result.Value.LastValidSlot); - Assert.AreEqual(296UL, result.Result.Value.LastValidBlockHeight); - - FinishTest(messageHandlerMock, TestnetUri); - } - - [TestMethod] - public void TestGetGenesisHash() - { - var responseData = File.ReadAllText("Resources/Http/GetGenesisHashResponse.json"); - var requestData = File.ReadAllText("Resources/Http/GetGenesisHashRequest.json"); - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri, - }; - - var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); - var result = sut.GetGenesisHash(); - - Assert.AreEqual(requestData, sentMessage); - Assert.IsNotNull(result.Result); - Assert.IsTrue(result.WasSuccessful); - Assert.AreEqual("4uhcVJyU9pJkvQyS88uRDiswHXSCkY3zQawwpjk2NsNY", result.Result); - - FinishTest(messageHandlerMock, TestnetUri); - } - - [TestMethod] - public void TestGetIdentity() - { - var responseData = File.ReadAllText("Resources/Http/GetIdentityResponse.json"); - var requestData = File.ReadAllText("Resources/Http/GetIdentityRequest.json"); - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri, - }; - - var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); - - var result = sut.GetIdentity(); - Assert.AreEqual(requestData, sentMessage); - Assert.IsNotNull(result.Result); - Assert.IsTrue(result.WasSuccessful); - Assert.AreEqual("2r1F4iWqVcb8M1DbAjQuFpebkQHY9hcVU4WuW2DJBppN", result.Result.Identity); - - FinishTest(messageHandlerMock, TestnetUri); - } - - [TestMethod] - public void TestGetInflationGovernor() - { - var responseData = File.ReadAllText("Resources/Http/GetInflationGovernorResponse.json"); - var requestData = File.ReadAllText("Resources/Http/GetInflationGovernorRequest.json"); - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri, - }; - - var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); - var result = sut.GetInflationGovernor(); - - Assert.AreEqual(requestData, sentMessage); - Assert.IsNotNull(result.Result); - Assert.IsTrue(result.WasSuccessful); - Assert.AreEqual((decimal)0.05, result.Result.Foundation); - Assert.AreEqual(7, result.Result.FoundationTerm); - Assert.AreEqual((decimal)0.15, result.Result.Initial); - Assert.AreEqual((decimal)0.15, result.Result.Taper); - Assert.AreEqual((decimal)0.015, result.Result.Terminal); - - FinishTest(messageHandlerMock, TestnetUri); - } - - [TestMethod] - public void TestGetInflationRate() - { - var responseData = File.ReadAllText("Resources/Http/GetInflationRateResponse.json"); - var requestData = File.ReadAllText("Resources/Http/GetInflationRateRequest.json"); - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri, - }; - - var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); - var result = sut.GetInflationRate(); - - Assert.AreEqual(requestData, sentMessage); - Assert.IsNotNull(result.Result); - Assert.IsTrue(result.WasSuccessful); - Assert.AreEqual(100, result.Result.Epoch); - Assert.AreEqual((decimal)0.149, result.Result.Total); - Assert.AreEqual((decimal)0.148, result.Result.Validator); - Assert.AreEqual((decimal)0.001, result.Result.Foundation); - - FinishTest(messageHandlerMock, TestnetUri); - } - - [TestMethod] - public void TestGetInflationReward() - { - var responseData = File.ReadAllText("Resources/Http/GetInflationRewardResponse.json"); - var requestData = File.ReadAllText("Resources/Http/GetInflationRewardRequest.json"); - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri, - }; - - var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); - var result = sut.GetInflationReward( - new List - { - "6dmNQ5jwLeLk5REvio1JcMshcbvkYMwy26sJ8pbkvStu", - "BGsqMegLpV6n6Ve146sSX2dTjUMj3M92HnU8BbNRMhF2" - }, 2); - - Assert.AreEqual(requestData, sentMessage); - Assert.IsNotNull(result.Result); - Assert.IsTrue(result.WasSuccessful); - Assert.AreEqual(2, result.Result.Count); - Assert.AreEqual(2500UL, result.Result[0].Amount); - Assert.AreEqual(224UL, result.Result[0].EffectiveSlot); - Assert.AreEqual(2UL, result.Result[0].Epoch); - Assert.AreEqual(499999442500UL, result.Result[0].PostBalance); - Assert.AreEqual(null, result.Result[1]); - - FinishTest(messageHandlerMock, TestnetUri); - } - - [TestMethod] - public void TestGetInflationRewardNoEpoch() - { - var responseData = File.ReadAllText("Resources/Http/GetInflationRewardNoEpochResponse.json"); - var requestData = File.ReadAllText("Resources/Http/GetInflationRewardNoEpochRequest.json"); - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri, - }; - - var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); - var result = sut.GetInflationReward( - new List - { - "25xzEf8cqLLEm2wyZTEBtCDchsUFm3SVESjs6eEFHJWe", - "GPQdoUUDQXM1gWgRVwBbYmDqAgxoZN3bhVeKr1P8jd4c" - }); - - Assert.AreEqual(requestData, sentMessage); - Assert.IsNotNull(result.Result); - Assert.IsTrue(result.WasSuccessful); - Assert.AreEqual(2, result.Result.Count); - Assert.AreEqual(1758149777313UL, result.Result[0].Amount); - Assert.AreEqual(81216004UL, result.Result[0].EffectiveSlot); - Assert.AreEqual(187UL, result.Result[0].Epoch); - Assert.AreEqual(1759149777313UL, result.Result[0].PostBalance); - Assert.AreEqual(null, result.Result[1]); - - FinishTest(messageHandlerMock, TestnetUri); - } - - [TestMethod] - public void TestGetLargestAccounts() - { - var responseData = File.ReadAllText("Resources/Http/GetLargestAccountsResponse.json"); - var requestData = File.ReadAllText("Resources/Http/GetLargestAccountsRequest.json"); - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri, - }; - - var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); - var result = sut.GetLargestAccounts("circulating"); - - Assert.AreEqual(requestData, sentMessage); - Assert.IsNotNull(result.Result); - Assert.IsTrue(result.WasSuccessful); - Assert.AreEqual(20, result.Result.Value.Count); - Assert.AreEqual("6caH6ayzofHnP8kcPQTEBrDPG4A2qDo1STE5xTMJ52k8", result.Result.Value[0].Address); - Assert.AreEqual(20161157050000000UL, result.Result.Value[0].Lamports); - Assert.AreEqual("gWgqQ4udVxE3uNxRHEwvftTHwpEmPHAd8JR9UzaHbR2", result.Result.Value[19].Address); - Assert.AreEqual(2499999990454560UL, result.Result.Value[19].Lamports); - - FinishTest(messageHandlerMock, TestnetUri); - } - - - [TestMethod] - public void TestGetMaxRetransmitSlot() - { - var responseData = File.ReadAllText("Resources/Http/GetMaxRetransmitSlotResponse.json"); - var requestData = File.ReadAllText("Resources/Http/GetMaxRetransmitSlotRequest.json"); - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri, - }; - - var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); - var result = sut.GetMaxRetransmitSlot(); - - Assert.AreEqual(requestData, sentMessage); - Assert.IsNotNull(result.Result); - Assert.IsTrue(result.WasSuccessful); - Assert.AreEqual(1234UL, result.Result); - - FinishTest(messageHandlerMock, TestnetUri); - } - - [TestMethod] - public void TestGetShredInsertSlot() - { - var responseData = File.ReadAllText("Resources/Http/GetMaxShredInsertSlotResponse.json"); - var requestData = File.ReadAllText("Resources/Http/GetMaxShredInsertSlotRequest.json"); - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri, - }; - - var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); - var result = sut.GetMaxShredInsertSlot(); - - Assert.AreEqual(requestData, sentMessage); - Assert.IsNotNull(result.Result); - Assert.IsTrue(result.WasSuccessful); - Assert.AreEqual(1234UL, result.Result); - - FinishTest(messageHandlerMock, TestnetUri); - } - - [TestMethod] - public void TestGetSlotLeadersEmpty() - { - var responseData = File.ReadAllText("Resources/Http/GetSlotLeadersEmptyResponse.json"); - var requestData = File.ReadAllText("Resources/Http/GetSlotLeadersEmptyRequest.json"); - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri, - }; - - var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); - var result = sut.GetSlotLeaders(0, 0); - - Assert.AreEqual(requestData, sentMessage); - Assert.IsNotNull(result.Result); - Assert.IsTrue(result.WasSuccessful); - - FinishTest(messageHandlerMock, TestnetUri); - } - - [TestMethod] - public void TestGetSlotLeaders() - { - var responseData = File.ReadAllText("Resources/Http/GetSlotLeadersResponse.json"); - var requestData = File.ReadAllText("Resources/Http/GetSlotLeadersRequest.json"); - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri, - }; - - var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); - var result = sut.GetSlotLeaders(100, 10); - - Assert.AreEqual(requestData, sentMessage); - Assert.IsNotNull(result.Result); - Assert.IsTrue(result.WasSuccessful); - Assert.AreEqual(10, result.Result.Count); - Assert.AreEqual("ChorusmmK7i1AxXeiTtQgQZhQNiXYU84ULeaYF1EH15n", result.Result[0]); - Assert.AreEqual("Awes4Tr6TX8JDzEhCZY2QVNimT6iD1zWHzf1vNyGvpLM", result.Result[4]); - Assert.AreEqual("DWvDTSh3qfn88UoQTEKRV2JnLt5jtJAVoiCo3ivtMwXP", result.Result[8]); - - FinishTest(messageHandlerMock, TestnetUri); - } - - [TestMethod] - public void TestGetSlotLeader() - { - var responseData = File.ReadAllText("Resources/Http/GetSlotLeaderResponse.json"); - var requestData = File.ReadAllText("Resources/Http/GetSlotLeaderRequest.json"); - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri, - }; - - var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); - var result = sut.GetSlotLeader(); - - Assert.AreEqual(requestData, sentMessage); - Assert.IsNotNull(result.Result); - Assert.IsTrue(result.WasSuccessful); - Assert.AreEqual("ENvAW7JScgYq6o4zKZwewtkzzJgDzuJAFxYasvmEQdpS", result.Result); - - FinishTest(messageHandlerMock, TestnetUri); - } - - [TestMethod] - public void TestGetSlot() - { - var responseData = File.ReadAllText("Resources/Http/GetSlotResponse.json"); - var requestData = File.ReadAllText("Resources/Http/GetSlotRequest.json"); - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri, - }; - - var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); - var result = sut.GetSlot(); - - Assert.AreEqual(requestData, sentMessage); - Assert.IsNotNull(result.Result); - Assert.IsTrue(result.WasSuccessful); - Assert.AreEqual(1234UL, result.Result); - - FinishTest(messageHandlerMock, TestnetUri); - } - - [TestMethod] - public void TestGetRecentPerformanceSamples() - { - var responseData = File.ReadAllText("Resources/Http/GetRecentPerformanceSamplesResponse.json"); - var requestData = File.ReadAllText("Resources/Http/GetRecentPerformanceSamplesRequest.json"); - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri, - }; - - var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); - var result = sut.GetRecentPerformanceSamples(4); - - Assert.AreEqual(requestData, sentMessage); - Assert.IsNotNull(result.Result); - Assert.IsTrue(result.WasSuccessful); - Assert.AreEqual(4, result.Result.Count); - Assert.AreEqual(126UL, result.Result[0].NumSlots); - Assert.AreEqual(348125UL, result.Result[0].Slot); - Assert.AreEqual(126UL, result.Result[0].NumTransactions); - Assert.AreEqual(60, result.Result[0].SamplePeriodSecs); - - FinishTest(messageHandlerMock, TestnetUri); - } - - [TestMethod] - public void TestGetSignaturesForAddress() - { - var responseData = File.ReadAllText("Resources/Http/Signatures/GetSignaturesForAddressResponse.json"); - var requestData = File.ReadAllText("Resources/Http/Signatures/GetSignaturesForAddressRequest.json"); - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri, - }; - - var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); - var result = sut.GetSignaturesForAddress("4Rf9mGD7FeYknun5JczX5nGLTfQuS1GRjNVfkEMKE92b", limit: 3); - - Assert.AreEqual(requestData, sentMessage); - Assert.IsNotNull(result.Result); - Assert.IsTrue(result.WasSuccessful); - Assert.AreEqual(3, result.Result.Count); - Assert.AreEqual(1616245823UL, result.Result[0].BlockTime); - Assert.AreEqual(68710495UL, result.Result[0].Slot); - Assert.AreEqual("5Jofwx5JcPT1dMsgo6DkyT6x61X5chS9K7hM7huGKAnUq8xxHwGKuDnnZmPGoapWVZcN4cPvQtGNCicnWZfPHowr", result.Result[0].Signature); - Assert.AreEqual(null, result.Result[0].Memo); - Assert.AreEqual(null, result.Result[0].Error); - - FinishTest(messageHandlerMock, TestnetUri); - } - - [TestMethod] - public void TestGetSignaturesForAddressUntil() - { - var responseData = File.ReadAllText("Resources/Http/Signatures/GetSignaturesForAddressUntilResponse.json"); - var requestData = File.ReadAllText("Resources/Http/Signatures/GetSignaturesForAddressUntilRequest.json"); - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri, - }; - - var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); - var result = sut.GetSignaturesForAddress( - "Vote111111111111111111111111111111111111111", - 1, until: "Vote111111111111111111111111111111111111111"); - - Assert.AreEqual(requestData, sentMessage); - Assert.IsNotNull(result.Result); - Assert.IsTrue(result.WasSuccessful); - Assert.AreEqual(1, result.Result.Count); - Assert.AreEqual(null, result.Result[0].BlockTime); - Assert.AreEqual(114UL, result.Result[0].Slot); - Assert.AreEqual("5h6xBEauJ3PK6SWCZ1PGjBvj8vDdWG3KpwATGy1ARAXFSDwt8GFXM7W5Ncn16wmqokgpiKRLuS83KUxyZyv2sUYv", result.Result[0].Signature); - Assert.AreEqual(null, result.Result[0].Memo); - Assert.AreEqual(null, result.Result[0].Error); - - FinishTest(messageHandlerMock, TestnetUri); - } - - [TestMethod] - public void TestGetSignaturesForAddressBefore() - { - var responseData = File.ReadAllText("Resources/Http/Signatures/GetSignaturesForAddressBeforeResponse.json"); - var requestData = File.ReadAllText("Resources/Http/Signatures/GetSignaturesForAddressBeforeRequest.json"); - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri, - }; - - var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); - var result = sut.GetSignaturesForAddress( - "Vote111111111111111111111111111111111111111", - 1, before: "Vote111111111111111111111111111111111111111"); - - Assert.AreEqual(requestData, sentMessage); - Assert.IsNotNull(result.Result); - Assert.IsTrue(result.WasSuccessful); - Assert.AreEqual(1, result.Result.Count); - Assert.AreEqual(null, result.Result[0].BlockTime); - Assert.AreEqual(114UL, result.Result[0].Slot); - Assert.AreEqual("5h6xBEauJ3PK6SWCZ1PGjBvj8vDdWG3KpwATGy1ARAXFSDwt8GFXM7W5Ncn16wmqokgpiKRLuS83KUxyZyv2sUYv", result.Result[0].Signature); - Assert.AreEqual(null, result.Result[0].Memo); - Assert.AreEqual(null, result.Result[0].Error); - - FinishTest(messageHandlerMock, TestnetUri); - } - - [TestMethod] - public void TestGetSignatureStatuses() - { - var responseData = File.ReadAllText("Resources/Http/Signatures/GetSignatureStatusesResponse.json"); - var requestData = File.ReadAllText("Resources/Http/Signatures/GetSignatureStatusesRequest.json"); - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri, - }; - - var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); - var result = sut.GetSignatureStatuses( - new List - { - "5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW", - "5j7s6NiJS3JAkvgkoc18WVAsiSaci2pxB2A6ueCJP4tprA2TFg9wSyTLeYouxPBJEMzJinENTkpA52YStRW5Dia7" - }); - - Assert.AreEqual(requestData, sentMessage); - Assert.IsNotNull(result.Result); - Assert.IsTrue(result.WasSuccessful); - Assert.AreEqual(82UL, result.Result.Context.Slot); - Assert.AreEqual(2, result.Result.Value.Count); - Assert.AreEqual(null, result.Result.Value[1]); - Assert.AreEqual(72UL, result.Result.Value[0].Slot); - Assert.AreEqual(10UL, result.Result.Value[0].Confirmations); - Assert.AreEqual("confirmed", result.Result.Value[0].ConfirmationStatus); - - FinishTest(messageHandlerMock, TestnetUri); - } - - [TestMethod] - public void TestGetSignatureStatusesWithHistory() - { - var responseData = File.ReadAllText("Resources/Http/Signatures/GetSignatureStatusesWithHistoryResponse.json"); - var requestData = File.ReadAllText("Resources/Http/Signatures/GetSignatureStatusesWithHistoryRequest.json"); - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri, - }; - - var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); - var result = sut.GetSignatureStatuses( - new List - { - "5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW", - "5j7s6NiJS3JAkvgkoc18WVAsiSaci2pxB2A6ueCJP4tprA2TFg9wSyTLeYouxPBJEMzJinENTkpA52YStRW5Dia7" - }, true); - - Assert.AreEqual(requestData, sentMessage); - Assert.IsNotNull(result.Result); - Assert.IsTrue(result.WasSuccessful); - Assert.AreEqual(82UL, result.Result.Context.Slot); - Assert.AreEqual(2, result.Result.Value.Count); - Assert.AreEqual(null, result.Result.Value[1]); - Assert.AreEqual(48UL, result.Result.Value[0].Slot); - Assert.AreEqual(null, result.Result.Value[0].Confirmations); - Assert.AreEqual("finalized", result.Result.Value[0].ConfirmationStatus); - - FinishTest(messageHandlerMock, TestnetUri); - } - - [TestMethod] - public void TestGetSnapshotSlot() - { - var responseData = File.ReadAllText("Resources/Http/GetSnapshotSlotResponse.json"); - var requestData = File.ReadAllText("Resources/Http/GetSnapshotSlotRequest.json"); - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri, - }; - - var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); - var result = sut.GetSnapshotSlot(); - - Assert.AreEqual(requestData, sentMessage); - Assert.IsNotNull(result.Result); - Assert.IsTrue(result.WasSuccessful); - Assert.AreEqual(100UL, result.Result); - - FinishTest(messageHandlerMock, TestnetUri); - } - - [TestMethod] - public void TestGetSupply() - { - var responseData = File.ReadAllText("Resources/Http/GetSupplyResponse.json"); - var requestData = File.ReadAllText("Resources/Http/GetSupplyRequest.json"); - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri, - }; - - var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); - var result = sut.GetSupply(); - - Assert.AreEqual(requestData, sentMessage); - Assert.IsNotNull(result.Result); - Assert.IsTrue(result.WasSuccessful); - Assert.AreEqual(79266564UL, result.Result.Context.Slot); - Assert.AreEqual(1359481823340465122UL, result.Result.Value.Circulating); - Assert.AreEqual(122260000000UL, result.Result.Value.NonCirculating); - Assert.AreEqual(16, result.Result.Value.NonCirculatingAccounts.Count); - - FinishTest(messageHandlerMock, TestnetUri); - } - - [TestMethod] - public void TestGetStakeActivation() - { - var responseData = File.ReadAllText("Resources/Http/GetStakeActivationResponse.json"); - var requestData = File.ReadAllText("Resources/Http/GetStakeActivationRequest.json"); - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri, - }; - - var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); - var result = sut.GetStakeActivation("CYRJWqiSjLitBAcRxPvWpgX3s5TvmN2SuRY3eEYypFvT"); - - Assert.AreEqual(requestData, sentMessage); - Assert.IsNotNull(result.Result); - Assert.IsTrue(result.WasSuccessful); - Assert.AreEqual(197717120UL, result.Result.Active); - Assert.AreEqual(0UL, result.Result.Inactive); - Assert.AreEqual("active", result.Result.State); - - FinishTest(messageHandlerMock, TestnetUri); - } - - [TestMethod] - public void TestGetStakeActivationWithEpoch() - { - var responseData = File.ReadAllText("Resources/Http/GetStakeActivationWithEpochResponse.json"); - var requestData = File.ReadAllText("Resources/Http/GetStakeActivationWithEpochRequest.json"); - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri, - }; - - var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); - var result = sut.GetStakeActivation("CYRJWqiSjLitBAcRxPvWpgX3s5TvmN2SuRY3eEYypFvT", 4); - - Assert.AreEqual(requestData, sentMessage); - Assert.IsNotNull(result.Result); - Assert.IsTrue(result.WasSuccessful); - Assert.AreEqual(124429280UL, result.Result.Active); - Assert.AreEqual(73287840UL, result.Result.Inactive); - Assert.AreEqual("activating", result.Result.State); - - FinishTest(messageHandlerMock, TestnetUri); - } - - [TestMethod] - public void TestGetTokenSupply() - { - var responseData = File.ReadAllText("Resources/Http/GetTokenSupplyResponse.json"); - var requestData = File.ReadAllText("Resources/Http/GetTokenSupplyRequest.json"); - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri, - }; - - var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); - var result = sut.GetTokenSupply("7ugkvt26sFjMdiFQFP5AQX8m8UkxWaW7rk2nBk4R6Gf2"); - - Assert.AreEqual(requestData, sentMessage); - Assert.IsNotNull(result.Result); - Assert.IsTrue(result.WasSuccessful); - Assert.AreEqual(79266576UL, result.Result.Context.Slot); - Assert.AreEqual("1000", result.Result.Value.Amount); - Assert.AreEqual(2, result.Result.Value.Decimals); - Assert.AreEqual("10", result.Result.Value.UiAmountString); - - FinishTest(messageHandlerMock, TestnetUri); - } - - [TestMethod] - [ExpectedException(typeof(AggregateException))] - public void TestGetTokenAccountsByOwnerException() - { - var httpClient = new HttpClient - { - BaseAddress = TestnetUri, - }; - - var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); - _ = sut.GetTokenAccountsByOwner("9we6kjtbcZ2vy3GSLLsZTEhbAqXPTRvEyoxa8wxSqKp5"); - } - - [TestMethod] - public void TestGetTokenAccountsByOwner() - { - var responseData = File.ReadAllText("Resources/Http/GetTokenAccountsByOwnerResponse.json"); - var requestData = File.ReadAllText("Resources/Http/GetTokenAccountsByOwnerRequest.json"); - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri, - }; - - var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); - var result = sut.GetTokenAccountsByOwner( - "9we6kjtbcZ2vy3GSLLsZTEhbAqXPTRvEyoxa8wxSqKp5", - tokenProgramId: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"); - - Assert.AreEqual(requestData, sentMessage); - Assert.IsNotNull(result.Result); - Assert.IsTrue(result.WasSuccessful); - Assert.AreEqual(79200468UL, result.Result.Context.Slot); - Assert.AreEqual(7, result.Result.Value.Count); - - FinishTest(messageHandlerMock, TestnetUri); - } - - [TestMethod] - public void TestGetTokenAccountsByDelegate() - { - var responseData = File.ReadAllText("Resources/Http/GetTokenAccountsByDelegateResponse.json"); - var requestData = File.ReadAllText("Resources/Http/GetTokenAccountsByDelegateRequest.json"); - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri, - }; - - var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); - var result = sut.GetTokenAccountsByDelegate( - "4Nd1mBQtrMJVYVfKf2PJy9NZUZdTAsp7D4xWLs4gDB4T", - tokenProgramId: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"); - - Assert.AreEqual(requestData, sentMessage); - Assert.IsNotNull(result.Result); - Assert.IsTrue(result.WasSuccessful); - Assert.AreEqual(1114UL, result.Result.Context.Slot); - Assert.AreEqual(1, result.Result.Value.Count); - Assert.AreEqual("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", result.Result.Value[0].Account.Owner); - Assert.AreEqual(false, result.Result.Value[0].Account.Executable); - Assert.AreEqual(4UL, result.Result.Value[0].Account.RentEpoch); - Assert.AreEqual(1726080UL, result.Result.Value[0].Account.Lamports); - Assert.AreEqual("4Nd1mBQtrMJVYVfKf2PJy9NZUZdTAsp7D4xWLs4gDB4T", - result.Result.Value[0].Account.Data.Parsed.Info.Delegate); - Assert.AreEqual(1UL, result.Result.Value[0].Account.Data.Parsed.Info.DelegatedAmount); - - FinishTest(messageHandlerMock, TestnetUri); - } - - [TestMethod] - public void TestGetTokenAccountBalance() - { - var responseData = File.ReadAllText("Resources/Http/GetTokenAccountBalanceResponse.json"); - var requestData = File.ReadAllText("Resources/Http/GetTokenAccountBalanceRequest.json"); - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri, - }; - - var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); - var result = sut.GetTokenAccountBalance("7247amxcSBamBSKZJrqbj373CiJSa1v21cRav56C3WfZ"); - - Assert.AreEqual(requestData, sentMessage); - Assert.IsNotNull(result.Result); - Assert.IsTrue(result.WasSuccessful); - Assert.AreEqual(79207643UL, result.Result.Context.Slot); - Assert.AreEqual("1000", result.Result.Value.Amount); - Assert.AreEqual(2, result.Result.Value.Decimals); - Assert.AreEqual("10", result.Result.Value.UiAmountString); - - FinishTest(messageHandlerMock, TestnetUri); - } - - [TestMethod] - public void TestGetTokenLargestAccounts() - { - var responseData = File.ReadAllText("Resources/Http/GetTokenLargestAccountsResponse.json"); - var requestData = File.ReadAllText("Resources/Http/GetTokenLargestAccountsRequest.json"); - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri, - }; - - var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); - var result = sut.GetTokenLargestAccounts("7ugkvt26sFjMdiFQFP5AQX8m8UkxWaW7rk2nBk4R6Gf2"); - - Assert.AreEqual(requestData, sentMessage); - Assert.IsNotNull(result.Result); - Assert.IsTrue(result.WasSuccessful); - Assert.AreEqual(79207653UL, result.Result.Context.Slot); - Assert.AreEqual(1, result.Result.Value.Count); - Assert.AreEqual("7247amxcSBamBSKZJrqbj373CiJSa1v21cRav56C3WfZ", result.Result.Value[0].Address); - Assert.AreEqual("1000", result.Result.Value[0].Amount); - Assert.AreEqual(2, result.Result.Value[0].Decimals); - Assert.AreEqual("10", result.Result.Value[0].UiAmountString); - - FinishTest(messageHandlerMock, TestnetUri); - } - - [TestMethod] - public void TestGetMinimumLedgerSlot() - { - var responseData = File.ReadAllText("Resources/Http/GetMinimumLedgerSlotResponse.json"); - var requestData = File.ReadAllText("Resources/Http/GetMinimumLedgerSlotRequest.json"); - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri, - }; - - var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); - var result = sut.GetMinimumLedgerSlot(); - - Assert.AreEqual(requestData, sentMessage); - Assert.IsNotNull(result.Result); - Assert.IsTrue(result.WasSuccessful); - Assert.AreEqual(78969229UL, result.Result); - - FinishTest(messageHandlerMock, TestnetUri); - } - - [TestMethod] - public void TestGetVersion() - { - var responseData = File.ReadAllText("Resources/Http/GetVersionResponse.json"); - var requestData = File.ReadAllText("Resources/Http/GetVersionRequest.json"); - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri, - }; - - var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); - var result = sut.GetVersion(); - - Assert.AreEqual(requestData, sentMessage); - Assert.IsNotNull(result.Result); - Assert.IsTrue(result.WasSuccessful); - Assert.AreEqual(1082270801UL, result.Result.FeatureSet); - Assert.AreEqual("1.6.11", result.Result.SolanaCore); - - FinishTest(messageHandlerMock, TestnetUri); - } - - [TestMethod] - public void TestGetVoteAccounts() - { - var responseData = File.ReadAllText("Resources/Http/GetVoteAccountsResponse.json"); - var requestData = File.ReadAllText("Resources/Http/GetVoteAccountsRequest.json"); - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri, - }; - - var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); - var result = sut.GetVoteAccounts(); - - Assert.AreEqual(requestData, sentMessage); - Assert.IsNotNull(result.Result); - Assert.IsTrue(result.WasSuccessful); - Assert.AreEqual(1, result.Result.Current.Length); - Assert.AreEqual(1, result.Result.Delinquent.Length); - - Assert.AreEqual(81274518UL, result.Result.Current[0].RootSlot); - Assert.AreEqual("3ZT31jkAGhUaw8jsy4bTknwBMP8i4Eueh52By4zXcsVw", result.Result.Current[0].VotePublicKey); - Assert.AreEqual("B97CCUW3AEZFGy6uUg6zUdnNYvnVq5VG8PUtb2HayTDD", result.Result.Current[0].NodePublicKey); - Assert.AreEqual(42UL, result.Result.Current[0].ActivatedStake); - Assert.AreEqual(0, result.Result.Current[0].Commission); - Assert.AreEqual(147UL, result.Result.Current[0].LastVote); - Assert.AreEqual(true, result.Result.Current[0].EpochVoteAccount); - Assert.AreEqual(2, result.Result.Current[0].EpochCredits.Length); - - Assert.AreEqual(1234UL, result.Result.Delinquent[0].RootSlot); - Assert.AreEqual("CmgCk4aMS7KW1SHX3s9K5tBJ6Yng2LBaC8MFov4wx9sm", result.Result.Delinquent[0].VotePublicKey); - Assert.AreEqual("6ZPxeQaDo4bkZLRsdNrCzchNQr5LN9QMc9sipXv9Kw8f", result.Result.Delinquent[0].NodePublicKey); - Assert.AreEqual(0UL, result.Result.Delinquent[0].ActivatedStake); - Assert.AreEqual(false, result.Result.Delinquent[0].EpochVoteAccount); - Assert.AreEqual(127UL, result.Result.Delinquent[0].Commission); - Assert.AreEqual(0UL, result.Result.Delinquent[0].LastVote); - Assert.AreEqual(0, result.Result.Delinquent[0].EpochCredits.Length); - - FinishTest(messageHandlerMock, TestnetUri); - } - - [TestMethod] - public void TestGetTransactionCount() - { - var responseData = File.ReadAllText("Resources/Http/GetTransactionCountResponse.json"); - var requestData = File.ReadAllText("Resources/Http/GetTransactionCountRequest.json"); - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri, - }; - - var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); - var result = sut.GetTransactionCount(); - - Assert.AreEqual(requestData, sentMessage); - Assert.IsNotNull(result.Result); - Assert.IsTrue(result.WasSuccessful); - Assert.AreEqual(23632393337UL, result.Result); - - FinishTest(messageHandlerMock, TestnetUri); - } - - [TestMethod] - public void TestSendTransaction() - { - var responseData = File.ReadAllText("Resources/Http/SendTransactionResponse.json"); - var requestData = File.ReadAllText("Resources/Http/SendTransactionRequest.json"); - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri, - }; - - var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); - var txData = "ASIhFkj3HRTDLiPxrxudL7eXCQ3DKrBB6Go/pn0sHWYIYgIHWYu2jZjbDXQseCEu73Li53BP7AEt8lCwKz" + - "X5awcBAAIER2mrlyBLqD\u002Bwyu4X94aPHgdOUhWBoNidlDedqmW3F7J7rHLZwOnCKOnqrRmjOO1w2JcV0XhP" + - "LlWiw5thiFgQQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABUpTUPhdyILWFKVWcniKKW3fHqur0KY" + - "GeIhJMvTu9qCKNNRNmSFNMnUzw5\u002BFDszWV6YvuvspBr0qlIoAdeg67wICAgABDAIAAACAlpgAAAAAAAMBA" + - "BVIZWxsbyBmcm9tIFNvbC5OZXQgOik="; - - var result = sut.SendTransaction(txData); - - Assert.AreEqual(requestData, sentMessage); - Assert.IsNotNull(result.Result); - Assert.IsTrue(result.WasSuccessful); - Assert.AreEqual("gaSFQXFqbYQypZdMFZy4Fe7uB2VFDEo4sGDypyrVxFgzZqc5MqWnRWTT9hXamcrFRcsiiH15vWii5ACSsyNScbp", - result.Result); - - FinishTest(messageHandlerMock, TestnetUri); - } - - [TestMethod] - public void TestSimulateTransaction() - { - var responseData = File.ReadAllText("Resources/Http/SimulateTransactionResponse.json"); - var requestData = File.ReadAllText("Resources/Http/SimulateTransactionRequest.json"); - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri, - }; - - var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); - var txData = "ASIhFkj3HRTDLiPxrxudL7eXCQ3DKrBB6Go/pn0sHWYIYgIHWYu2jZjbDXQseCEu73Li53BP7AEt8lCwKz" + - "X5awcBAAIER2mrlyBLqD\u002Bwyu4X94aPHgdOUhWBoNidlDedqmW3F7J7rHLZwOnCKOnqrRmjOO1w2JcV0XhP" + - "LlWiw5thiFgQQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABUpTUPhdyILWFKVWcniKKW3fHqur0KY" + - "GeIhJMvTu9qCKNNRNmSFNMnUzw5\u002BFDszWV6YvuvspBr0qlIoAdeg67wICAgABDAIAAACAlpgAAAAAAAMBA" + - "BVIZWxsbyBmcm9tIFNvbC5OZXQgOik="; - - var result = sut.SimulateTransaction(txData); - - Assert.AreEqual(requestData, sentMessage); - Assert.IsNotNull(result.Result.Value); - Assert.AreEqual(79206888UL, result.Result.Context.Slot); - Assert.AreEqual(null, result.Result.Value.Error); - Assert.AreEqual(5, result.Result.Value.Logs.Length); - - FinishTest(messageHandlerMock, TestnetUri); - } - - [TestMethod] - public void TestSimulateTransactionInsufficientLamports() - { - var responseData = File.ReadAllText("Resources/Http/SimulateTransactionInsufficientLamportsResponse.json"); - var requestData = File.ReadAllText("Resources/Http/SimulateTransactionInsufficientLamportsRequest.json"); - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri, - }; - - var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); - var txData = "ARymmnVB6PB0x//jV2vsTFFdeOkzD0FFoQq6P\u002BwzGKlMD\u002BXLb/hWnOebNaYlg/" + - "\u002Bj6jdm9Fe2Sba/ACnvcv9KIA4BAAIEUy4zulRg8z2yKITZaNwcnq6G6aH8D0ITae862qbJ" + - "\u002B3eE3M6r5DRwldquwlqOuXDDOWZagXmbHnAU3w5Dg44kogAAAAAAAAAAAAAAAAAAAAAAAA" + - "AAAAAAAAAAAAAAAAAABUpTUPhdyILWFKVWcniKKW3fHqur0KYGeIhJMvTu9qBann0itTd6uxx69h" + - "ION5Js4E4drRP8CWwoLTdorAFUqAICAgABDAIAAACAlpgAAAAAAAMBABVIZWxsbyBmcm9tIFNvbC5OZXQgOik="; - - var result = sut.SimulateTransaction(txData); - - Assert.AreEqual(requestData, sentMessage); - Assert.IsNotNull(result.Result.Value); - Assert.AreEqual(79203980UL, result.Result.Context.Slot); - Assert.AreEqual(3, result.Result.Value.Logs.Length); - Assert.IsNotNull(result.Result.Value.Error); - Assert.AreEqual(TransactionErrorType.InstructionError, result.Result.Value.Error.Type); - Assert.IsNotNull(result.Result.Value.Error.InstructionError); - Assert.AreEqual(InstructionErrorType.Custom, result.Result.Value.Error.InstructionError.Type); - Assert.AreEqual(1, result.Result.Value.Error.InstructionError.CustomError); - FinishTest(messageHandlerMock, TestnetUri); - } - - - [TestMethod] - public void TestGetHealth_HealthyResponse() - { - - var responseData = File.ReadAllText("Resources/Http/Health/GetHealthHealthyResponse.json"); - var requestData = File.ReadAllText("Resources/Http/Health/GetHealthRequest.json"); - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri - }; - var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); - - var res = sut.GetHealth(); - - Assert.AreEqual(requestData, sentMessage); - Assert.AreEqual("ok", res.Result); - - FinishTest(messageHandlerMock, TestnetUri); - } - - [TestMethod] - public void TestGetHealth_UnhealthyResponse() - { - - var responseData = File.ReadAllText("Resources/Http/Health/GetHealthUnhealthyResponse.json"); - var requestData = File.ReadAllText("Resources/Http/Health/GetHealthRequest.json"); - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri - }; - var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); - - var res = sut.GetHealth(); - - Assert.AreEqual(requestData, sentMessage); - Assert.IsNull(res.Result); - Assert.IsTrue(res.WasHttpRequestSuccessful); - Assert.IsFalse(res.WasRequestSuccessfullyHandled); - Assert.AreEqual(-32005, res.ServerErrorCode); - Assert.AreEqual("Node is behind by 42 slots", res.Reason); - - FinishTest(messageHandlerMock, TestnetUri); - } - - [TestMethod] - public void TestGetLeaderSchedule_SlotArgsRequest() - { - - var responseData = File.ReadAllText("Resources/Http/LeaderSchedule/GetLeaderScheduleResponse.json"); - var requestData = File.ReadAllText("Resources/Http/LeaderSchedule/GetLeaderScheduleSlotArgsRequest.json"); - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri - }; - var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); - - var res = sut.GetLeaderSchedule(79700000); - - Assert.AreEqual(requestData, sentMessage); - Assert.IsNotNull(res.Result); - Assert.IsTrue(res.WasSuccessful); - - Assert.AreEqual(2, res.Result.Count); - Assert.IsTrue(res.Result.ContainsKey("4Qkev8aNZcqFNSRhQzwyLMFSsi94jHqE8WNVTJzTP99F")); - - Assert.AreEqual(7, res.Result["4Qkev8aNZcqFNSRhQzwyLMFSsi94jHqE8WNVTJzTP99F"].Count); - Assert.AreEqual(0UL, res.Result["4Qkev8aNZcqFNSRhQzwyLMFSsi94jHqE8WNVTJzTP99F"][0]); - - FinishTest(messageHandlerMock, TestnetUri); - } - - [TestMethod] - public void TestGetLeaderSchedule_IdentityArgsRequest() - { - - var responseData = File.ReadAllText("Resources/Http/LeaderSchedule/GetLeaderScheduleResponse.json"); - var requestData = File.ReadAllText("Resources/Http/LeaderSchedule/GetLeaderScheduleIdentityArgsRequest.json"); - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri - }; - var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); - - var res = sut.GetLeaderSchedule(identity: "Bbe9EKucmRtJr2J4dd5Eb5ybQmY7Fm7jYxKXxmmkLFsu"); - - Assert.AreEqual(requestData, sentMessage); - Assert.IsNotNull(res.Result); - Assert.IsTrue(res.WasSuccessful); - - Assert.AreEqual(2, res.Result.Count); - Assert.IsTrue(res.Result.ContainsKey("4Qkev8aNZcqFNSRhQzwyLMFSsi94jHqE8WNVTJzTP99F")); - - Assert.AreEqual(7, res.Result["4Qkev8aNZcqFNSRhQzwyLMFSsi94jHqE8WNVTJzTP99F"].Count); - Assert.AreEqual(0UL, res.Result["4Qkev8aNZcqFNSRhQzwyLMFSsi94jHqE8WNVTJzTP99F"][0]); - - FinishTest(messageHandlerMock, TestnetUri); - } - - [TestMethod] - public void TestGetLeaderSchedule_SlotIdentityArgsRequest() - { - - var responseData = File.ReadAllText("Resources/Http/LeaderSchedule/GetLeaderScheduleResponse.json"); - var requestData = File.ReadAllText("Resources/Http/LeaderSchedule/GetLeaderScheduleSlotIdentityArgsRequest.json"); - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri - }; - var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); - - var res = sut.GetLeaderSchedule(79700000, "Bbe9EKucmRtJr2J4dd5Eb5ybQmY7Fm7jYxKXxmmkLFsu"); - - Assert.AreEqual(requestData, sentMessage); - Assert.IsNotNull(res.Result); - Assert.IsTrue(res.WasSuccessful); - - Assert.AreEqual(2, res.Result.Count); - Assert.IsTrue(res.Result.ContainsKey("4Qkev8aNZcqFNSRhQzwyLMFSsi94jHqE8WNVTJzTP99F")); - - Assert.AreEqual(7, res.Result["4Qkev8aNZcqFNSRhQzwyLMFSsi94jHqE8WNVTJzTP99F"].Count); - Assert.AreEqual(0UL, res.Result["4Qkev8aNZcqFNSRhQzwyLMFSsi94jHqE8WNVTJzTP99F"][0]); - - FinishTest(messageHandlerMock, TestnetUri); - } - - [TestMethod] - public void TestGetLeaderSchedule_NoArgsRequest() - { - - var responseData = File.ReadAllText("Resources/Http/LeaderSchedule/GetLeaderScheduleResponse.json"); - var requestData = File.ReadAllText("Resources/Http/LeaderSchedule/GetLeaderScheduleNoArgsRequest.json"); - var sentMessage = string.Empty; - var messageHandlerMock = SetupTest( - (s => sentMessage = s), responseData); - - var httpClient = new HttpClient(messageHandlerMock.Object) - { - BaseAddress = TestnetUri - }; - var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); - - var res = sut.GetLeaderSchedule(); - - Assert.AreEqual(requestData, sentMessage); - Assert.IsNotNull(res.Result); - Assert.IsTrue(res.WasSuccessful); - - Assert.AreEqual(2, res.Result.Count); - Assert.IsTrue(res.Result.ContainsKey("4Qkev8aNZcqFNSRhQzwyLMFSsi94jHqE8WNVTJzTP99F")); - - Assert.AreEqual(7, res.Result["4Qkev8aNZcqFNSRhQzwyLMFSsi94jHqE8WNVTJzTP99F"].Count); - Assert.AreEqual(0UL, res.Result["4Qkev8aNZcqFNSRhQzwyLMFSsi94jHqE8WNVTJzTP99F"][0]); - - FinishTest(messageHandlerMock, TestnetUri); - } - } +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Moq.Protected; +using Solnet.Rpc.Models; +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace Solnet.Rpc.Test +{ + [TestClass] + public partial class SolanaRpcClientTest + { + private const string TestnetUrl = "https://testnet.solana.com"; + private static readonly Uri TestnetUri = new Uri(TestnetUrl); + + /// + /// Setup the test with the request and response data. + /// + /// Capture the sent content. + /// The response content. + private Mock SetupTest(Action sentPayloadCapture, string responseContent) + { + var messageHandlerMock = new Mock(MockBehavior.Strict); + messageHandlerMock + .Protected() + .Setup>( + "SendAsync", + ItExpr.Is( + message => message.Method == HttpMethod.Post && + message.RequestUri == TestnetUri), + ItExpr.IsAny() + ) + .Callback((httpRequest, ct) => + sentPayloadCapture(httpRequest.Content.ReadAsStringAsync(ct).Result)) + .ReturnsAsync(new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + Content = new StringContent(responseContent), + }) + .Verifiable(); + return messageHandlerMock; + } + + /// + /// Finish the test by asserting the http request went as expected. + /// + /// The request uri. + private void FinishTest(Mock mockHandler, Uri expectedUri) + { + mockHandler.Protected().Verify( + "SendAsync", + Times.Exactly(1), + ItExpr.Is(req => + req.Method == HttpMethod.Post + && req.RequestUri == expectedUri + ), + ItExpr.IsAny() + ); + } + + [TestMethod] + public void TestEmptyPayloadRequest() + { + var responseData = File.ReadAllText("Resources/Http/EmptyPayloadResponse.json"); + var requestData = File.ReadAllText("Resources/Http/EmptyPayloadRequest.json"); + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri, + }; + + var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); + + var result = sut.GetBalance(""); + + Assert.AreEqual(requestData, sentMessage); + Assert.IsNotNull(result); + Assert.IsNull(result.Result); + Assert.IsTrue(result.WasHttpRequestSuccessful); + Assert.IsFalse(result.WasRequestSuccessfullyHandled); + Assert.IsFalse(result.WasSuccessful); + Assert.AreEqual("Invalid param: WrongSize", result.Reason); + Assert.AreEqual(-32602, result.ServerErrorCode); + } + + [TestMethod] + public void TestBadAddressExceptionRequest() + { + var msg = "something bad happenned"; + var responseData = File.ReadAllText("Resources/Http/EmptyPayloadResponse.json"); + var requestData = File.ReadAllText("Resources/Http/EmptyPayloadRequest.json"); + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + messageHandlerMock.Protected().Setup( + "SendAsync", + ItExpr.IsAny(), + ItExpr.IsAny() + ).Throws(new HttpRequestException(msg)); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri, + }; + + var sut = new SolanaRpcClient("https://non.existing.adddress.com", null, httpClient); + + var result = sut.GetBalance(""); + Assert.AreEqual(HttpStatusCode.BadRequest, result.HttpStatusCode); + Assert.AreEqual(msg, result.Reason); + Assert.IsFalse(result.WasHttpRequestSuccessful); + Assert.IsFalse(result.WasRequestSuccessfullyHandled); + } + + [TestMethod] + public void TestBadAddress2ExceptionRequest() + { + var msg = "not found bro"; + var responseData = File.ReadAllText("Resources/Http/EmptyPayloadResponse.json"); + var requestData = File.ReadAllText("Resources/Http/EmptyPayloadRequest.json"); + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + messageHandlerMock.Protected().Setup( + "SendAsync", + ItExpr.IsAny(), + ItExpr.IsAny() + ).Throws(new HttpRequestException(msg, null, HttpStatusCode.NotFound)); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri, + }; + + var sut = new SolanaRpcClient("https://valid.server.but.invalid.endpoint.com", null, httpClient); + + var result = sut.GetBalance(""); + Assert.AreEqual(HttpStatusCode.NotFound, result.HttpStatusCode); + Assert.AreEqual(msg, result.Reason); + Assert.IsFalse(result.WasHttpRequestSuccessful); + Assert.IsFalse(result.WasRequestSuccessfullyHandled); + } + + [TestMethod] + public void TestGetBalance() + { + var responseData = File.ReadAllText("Resources/Http/GetBalanceResponse.json"); + var requestData = File.ReadAllText("Resources/Http/GetBalanceRequest.json"); + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri, + }; + + var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); + var result = sut.GetBalance("hoakwpFB8UoLnPpLC56gsjpY7XbVwaCuRQRMQzN5TVh"); + + Assert.AreEqual(requestData, sentMessage); + Assert.IsNotNull(result.Result); + Assert.IsTrue(result.WasSuccessful); + Assert.AreEqual(79274779UL, result.Result.Context.Slot); + Assert.AreEqual(168855000000UL, result.Result.Value); + + FinishTest(messageHandlerMock, TestnetUri); + } + + [TestMethod] + public void TestGetBalanceConfirmed() + { + var responseData = File.ReadAllText("Resources/Http/GetBalanceResponse.json"); + var requestData = File.ReadAllText("Resources/Http/GetBalanceConfirmedRequest.json"); + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri, + }; + + var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); + var result = sut.GetBalance("hoakwpFB8UoLnPpLC56gsjpY7XbVwaCuRQRMQzN5TVh", Types.Commitment.Confirmed); + + Assert.AreEqual(requestData, sentMessage); + Assert.IsNotNull(result.Result); + Assert.IsTrue(result.WasSuccessful); + Assert.AreEqual(79274779UL, result.Result.Context.Slot); + Assert.AreEqual(168855000000UL, result.Result.Value); + + FinishTest(messageHandlerMock, TestnetUri); + } + + [TestMethod] + public void TestGetClusterNodes() + { + var responseData = File.ReadAllText("Resources/Http/GetClusterNodesResponse.json"); + var requestData = File.ReadAllText("Resources/Http/GetClusterNodesRequest.json"); + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri, + }; + + var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); + var result = sut.GetClusterNodes(); + + Assert.AreEqual(requestData, sentMessage); + Assert.IsNotNull(result.Result); + Assert.IsTrue(result.WasSuccessful); + Assert.AreEqual(5, result.Result.Count); + Assert.AreEqual(3533521759UL, result.Result[0].FeatureSet); + Assert.AreEqual("216.24.140.155:8001", result.Result[0].Gossip); + Assert.AreEqual("5D1fNXzvv5NjV1ysLjirC4WY92RNsVH18vjmcszZd8on", result.Result[0].PublicKey); + Assert.AreEqual("216.24.140.155:8899", result.Result[0].Rpc); + Assert.AreEqual(18122UL, result.Result[0].ShredVersion); + Assert.AreEqual("216.24.140.155:8004", result.Result[0].Tpu); + Assert.AreEqual("1.7.0", result.Result[0].Version); + + FinishTest(messageHandlerMock, TestnetUri); + } + + [TestMethod] + public void TestGetEpochInfo() + { + var responseData = File.ReadAllText("Resources/Http/GetEpochInfoResponse.json"); + var requestData = File.ReadAllText("Resources/Http/GetEpochInfoRequest.json"); + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri, + }; + + var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); + var result = sut.GetEpochInfo(); + + Assert.AreEqual(requestData, sentMessage); + Assert.IsNotNull(result.Result); + Assert.IsTrue(result.WasSuccessful); + Assert.AreEqual(166598UL, result.Result.AbsoluteSlot); + Assert.AreEqual(166500UL, result.Result.BlockHeight); + Assert.AreEqual(27UL, result.Result.Epoch); + Assert.AreEqual(2790UL, result.Result.SlotIndex); + Assert.AreEqual(8192UL, result.Result.SlotsInEpoch); + + FinishTest(messageHandlerMock, TestnetUri); + } + + [TestMethod] + public void TestGetEpochSchedule() + { + var responseData = File.ReadAllText("Resources/Http/GetEpochScheduleResponse.json"); + var requestData = File.ReadAllText("Resources/Http/GetEpochScheduleRequest.json"); + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri, + }; + + var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); + var result = sut.GetEpochSchedule(); + + Assert.AreEqual(requestData, sentMessage); + Assert.IsNotNull(result.Result); + Assert.IsTrue(result.WasSuccessful); + Assert.AreEqual(8UL, result.Result.FirstNormalEpoch); + Assert.AreEqual(8160UL, result.Result.FirstNormalSlot); + Assert.AreEqual(8192UL, result.Result.LeaderScheduleSlotOffset); + Assert.AreEqual(8192UL, result.Result.SlotsPerEpoch); + Assert.AreEqual(true, result.Result.Warmup); + + FinishTest(messageHandlerMock, TestnetUri); + } + + [TestMethod] + public void TestGetFeeRateGovernor() + { + var responseData = File.ReadAllText("Resources/Http/GetFeeRateGovernorResponse.json"); + var requestData = File.ReadAllText("Resources/Http/GetFeeRateGovernorRequest.json"); + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri, + }; + + var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); + var result = sut.GetFeeRateGovernor(); + + Assert.AreEqual(requestData, sentMessage); + Assert.IsNotNull(result.Result); + Assert.IsTrue(result.WasSuccessful); + Assert.AreEqual(54UL, result.Result.Context.Slot); + Assert.AreEqual(50, result.Result.Value.FeeRateGovernor.BurnPercent); + Assert.AreEqual(100000UL, result.Result.Value.FeeRateGovernor.MaxLamportsPerSignature); + Assert.AreEqual(5000UL, result.Result.Value.FeeRateGovernor.MinLamportsPerSignature); + Assert.AreEqual(10000UL, result.Result.Value.FeeRateGovernor.TargetLamportsPerSignature); + Assert.AreEqual(20000UL, result.Result.Value.FeeRateGovernor.TargetSignaturesPerSlot); + + FinishTest(messageHandlerMock, TestnetUri); + } + + [TestMethod] + public void TestGetFees() + { + var responseData = File.ReadAllText("Resources/Http/GetFeesResponse.json"); + var requestData = File.ReadAllText("Resources/Http/GetFeesRequest.json"); + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri, + }; + + var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); + var result = sut.GetFees(); + + Assert.AreEqual(requestData, sentMessage); + Assert.IsNotNull(result.Result); + Assert.IsTrue(result.WasSuccessful); + Assert.AreEqual(1UL, result.Result.Context.Slot); + Assert.AreEqual("CSymwgTNX1j3E4qhKfJAUE41nBWEwXufoYryPbkde5RR", result.Result.Value.Blockhash); + Assert.AreEqual(5000UL, result.Result.Value.FeeCalculator.LamportsPerSignature); + Assert.AreEqual(297UL, result.Result.Value.LastValidSlot); + Assert.AreEqual(296UL, result.Result.Value.LastValidBlockHeight); + + FinishTest(messageHandlerMock, TestnetUri); + } + + [TestMethod] + public void TestGetGenesisHash() + { + var responseData = File.ReadAllText("Resources/Http/GetGenesisHashResponse.json"); + var requestData = File.ReadAllText("Resources/Http/GetGenesisHashRequest.json"); + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri, + }; + + var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); + var result = sut.GetGenesisHash(); + + Assert.AreEqual(requestData, sentMessage); + Assert.IsNotNull(result.Result); + Assert.IsTrue(result.WasSuccessful); + Assert.AreEqual("4uhcVJyU9pJkvQyS88uRDiswHXSCkY3zQawwpjk2NsNY", result.Result); + + FinishTest(messageHandlerMock, TestnetUri); + } + + [TestMethod] + public void TestGetIdentity() + { + var responseData = File.ReadAllText("Resources/Http/GetIdentityResponse.json"); + var requestData = File.ReadAllText("Resources/Http/GetIdentityRequest.json"); + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri, + }; + + var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); + + var result = sut.GetIdentity(); + Assert.AreEqual(requestData, sentMessage); + Assert.IsNotNull(result.Result); + Assert.IsTrue(result.WasSuccessful); + Assert.AreEqual("2r1F4iWqVcb8M1DbAjQuFpebkQHY9hcVU4WuW2DJBppN", result.Result.Identity); + + FinishTest(messageHandlerMock, TestnetUri); + } + + [TestMethod] + public void TestGetInflationGovernor() + { + var responseData = File.ReadAllText("Resources/Http/GetInflationGovernorResponse.json"); + var requestData = File.ReadAllText("Resources/Http/GetInflationGovernorRequest.json"); + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri, + }; + + var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); + var result = sut.GetInflationGovernor(); + + Assert.AreEqual(requestData, sentMessage); + Assert.IsNotNull(result.Result); + Assert.IsTrue(result.WasSuccessful); + Assert.AreEqual((decimal)0.05, result.Result.Foundation); + Assert.AreEqual(7, result.Result.FoundationTerm); + Assert.AreEqual((decimal)0.15, result.Result.Initial); + Assert.AreEqual((decimal)0.15, result.Result.Taper); + Assert.AreEqual((decimal)0.015, result.Result.Terminal); + + FinishTest(messageHandlerMock, TestnetUri); + } + + [TestMethod] + public void TestGetInflationRate() + { + var responseData = File.ReadAllText("Resources/Http/GetInflationRateResponse.json"); + var requestData = File.ReadAllText("Resources/Http/GetInflationRateRequest.json"); + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri, + }; + + var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); + var result = sut.GetInflationRate(); + + Assert.AreEqual(requestData, sentMessage); + Assert.IsNotNull(result.Result); + Assert.IsTrue(result.WasSuccessful); + Assert.AreEqual(100, result.Result.Epoch); + Assert.AreEqual((decimal)0.149, result.Result.Total); + Assert.AreEqual((decimal)0.148, result.Result.Validator); + Assert.AreEqual((decimal)0.001, result.Result.Foundation); + + FinishTest(messageHandlerMock, TestnetUri); + } + + [TestMethod] + public void TestGetInflationReward() + { + var responseData = File.ReadAllText("Resources/Http/GetInflationRewardResponse.json"); + var requestData = File.ReadAllText("Resources/Http/GetInflationRewardRequest.json"); + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri, + }; + + var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); + var result = sut.GetInflationReward( + new List + { + "6dmNQ5jwLeLk5REvio1JcMshcbvkYMwy26sJ8pbkvStu", + "BGsqMegLpV6n6Ve146sSX2dTjUMj3M92HnU8BbNRMhF2" + }, 2); + + Assert.AreEqual(requestData, sentMessage); + Assert.IsNotNull(result.Result); + Assert.IsTrue(result.WasSuccessful); + Assert.AreEqual(2, result.Result.Count); + Assert.AreEqual(2500UL, result.Result[0].Amount); + Assert.AreEqual(224UL, result.Result[0].EffectiveSlot); + Assert.AreEqual(2UL, result.Result[0].Epoch); + Assert.AreEqual(499999442500UL, result.Result[0].PostBalance); + Assert.AreEqual(null, result.Result[1]); + + FinishTest(messageHandlerMock, TestnetUri); + } + + [TestMethod] + public void TestGetInflationRewardNoEpoch() + { + var responseData = File.ReadAllText("Resources/Http/GetInflationRewardNoEpochResponse.json"); + var requestData = File.ReadAllText("Resources/Http/GetInflationRewardNoEpochRequest.json"); + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri, + }; + + var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); + var result = sut.GetInflationReward( + new List + { + "25xzEf8cqLLEm2wyZTEBtCDchsUFm3SVESjs6eEFHJWe", + "GPQdoUUDQXM1gWgRVwBbYmDqAgxoZN3bhVeKr1P8jd4c" + }); + + Assert.AreEqual(requestData, sentMessage); + Assert.IsNotNull(result.Result); + Assert.IsTrue(result.WasSuccessful); + Assert.AreEqual(2, result.Result.Count); + Assert.AreEqual(1758149777313UL, result.Result[0].Amount); + Assert.AreEqual(81216004UL, result.Result[0].EffectiveSlot); + Assert.AreEqual(187UL, result.Result[0].Epoch); + Assert.AreEqual(1759149777313UL, result.Result[0].PostBalance); + Assert.AreEqual(null, result.Result[1]); + + FinishTest(messageHandlerMock, TestnetUri); + } + + [TestMethod] + public void TestGetLargestAccounts() + { + var responseData = File.ReadAllText("Resources/Http/GetLargestAccountsResponse.json"); + var requestData = File.ReadAllText("Resources/Http/GetLargestAccountsRequest.json"); + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri, + }; + + var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); + var result = sut.GetLargestAccounts("circulating"); + + Assert.AreEqual(requestData, sentMessage); + Assert.IsNotNull(result.Result); + Assert.IsTrue(result.WasSuccessful); + Assert.AreEqual(20, result.Result.Value.Count); + Assert.AreEqual("6caH6ayzofHnP8kcPQTEBrDPG4A2qDo1STE5xTMJ52k8", result.Result.Value[0].Address); + Assert.AreEqual(20161157050000000UL, result.Result.Value[0].Lamports); + Assert.AreEqual("gWgqQ4udVxE3uNxRHEwvftTHwpEmPHAd8JR9UzaHbR2", result.Result.Value[19].Address); + Assert.AreEqual(2499999990454560UL, result.Result.Value[19].Lamports); + + FinishTest(messageHandlerMock, TestnetUri); + } + + + [TestMethod] + public void TestGetMaxRetransmitSlot() + { + var responseData = File.ReadAllText("Resources/Http/GetMaxRetransmitSlotResponse.json"); + var requestData = File.ReadAllText("Resources/Http/GetMaxRetransmitSlotRequest.json"); + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri, + }; + + var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); + var result = sut.GetMaxRetransmitSlot(); + + Assert.AreEqual(requestData, sentMessage); + Assert.IsNotNull(result.Result); + Assert.IsTrue(result.WasSuccessful); + Assert.AreEqual(1234UL, result.Result); + + FinishTest(messageHandlerMock, TestnetUri); + } + + [TestMethod] + public void TestGetShredInsertSlot() + { + var responseData = File.ReadAllText("Resources/Http/GetMaxShredInsertSlotResponse.json"); + var requestData = File.ReadAllText("Resources/Http/GetMaxShredInsertSlotRequest.json"); + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri, + }; + + var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); + var result = sut.GetMaxShredInsertSlot(); + + Assert.AreEqual(requestData, sentMessage); + Assert.IsNotNull(result.Result); + Assert.IsTrue(result.WasSuccessful); + Assert.AreEqual(1234UL, result.Result); + + FinishTest(messageHandlerMock, TestnetUri); + } + + [TestMethod] + public void TestGetSlotLeadersEmpty() + { + var responseData = File.ReadAllText("Resources/Http/GetSlotLeadersEmptyResponse.json"); + var requestData = File.ReadAllText("Resources/Http/GetSlotLeadersEmptyRequest.json"); + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri, + }; + + var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); + var result = sut.GetSlotLeaders(0, 0); + + Assert.AreEqual(requestData, sentMessage); + Assert.IsNotNull(result.Result); + Assert.IsTrue(result.WasSuccessful); + + FinishTest(messageHandlerMock, TestnetUri); + } + + [TestMethod] + public void TestGetSlotLeaders() + { + var responseData = File.ReadAllText("Resources/Http/GetSlotLeadersResponse.json"); + var requestData = File.ReadAllText("Resources/Http/GetSlotLeadersRequest.json"); + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri, + }; + + var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); + var result = sut.GetSlotLeaders(100, 10); + + Assert.AreEqual(requestData, sentMessage); + Assert.IsNotNull(result.Result); + Assert.IsTrue(result.WasSuccessful); + Assert.AreEqual(10, result.Result.Count); + Assert.AreEqual("ChorusmmK7i1AxXeiTtQgQZhQNiXYU84ULeaYF1EH15n", result.Result[0]); + Assert.AreEqual("Awes4Tr6TX8JDzEhCZY2QVNimT6iD1zWHzf1vNyGvpLM", result.Result[4]); + Assert.AreEqual("DWvDTSh3qfn88UoQTEKRV2JnLt5jtJAVoiCo3ivtMwXP", result.Result[8]); + + FinishTest(messageHandlerMock, TestnetUri); + } + + [TestMethod] + public void TestGetSlotLeader() + { + var responseData = File.ReadAllText("Resources/Http/GetSlotLeaderResponse.json"); + var requestData = File.ReadAllText("Resources/Http/GetSlotLeaderRequest.json"); + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri, + }; + + var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); + var result = sut.GetSlotLeader(); + + Assert.AreEqual(requestData, sentMessage); + Assert.IsNotNull(result.Result); + Assert.IsTrue(result.WasSuccessful); + Assert.AreEqual("ENvAW7JScgYq6o4zKZwewtkzzJgDzuJAFxYasvmEQdpS", result.Result); + + FinishTest(messageHandlerMock, TestnetUri); + } + + [TestMethod] + public void TestGetSlot() + { + var responseData = File.ReadAllText("Resources/Http/GetSlotResponse.json"); + var requestData = File.ReadAllText("Resources/Http/GetSlotRequest.json"); + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri, + }; + + var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); + var result = sut.GetSlot(); + + Assert.AreEqual(requestData, sentMessage); + Assert.IsNotNull(result.Result); + Assert.IsTrue(result.WasSuccessful); + Assert.AreEqual(1234UL, result.Result); + + FinishTest(messageHandlerMock, TestnetUri); + } + + [TestMethod] + public void TestGetRecentPerformanceSamples() + { + var responseData = File.ReadAllText("Resources/Http/GetRecentPerformanceSamplesResponse.json"); + var requestData = File.ReadAllText("Resources/Http/GetRecentPerformanceSamplesRequest.json"); + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri, + }; + + var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); + var result = sut.GetRecentPerformanceSamples(4); + + Assert.AreEqual(requestData, sentMessage); + Assert.IsNotNull(result.Result); + Assert.IsTrue(result.WasSuccessful); + Assert.AreEqual(4, result.Result.Count); + Assert.AreEqual(126UL, result.Result[0].NumSlots); + Assert.AreEqual(348125UL, result.Result[0].Slot); + Assert.AreEqual(126UL, result.Result[0].NumTransactions); + Assert.AreEqual(60, result.Result[0].SamplePeriodSecs); + + FinishTest(messageHandlerMock, TestnetUri); + } + + [TestMethod] + public void TestGetSignaturesForAddress() + { + var responseData = File.ReadAllText("Resources/Http/Signatures/GetSignaturesForAddressResponse.json"); + var requestData = File.ReadAllText("Resources/Http/Signatures/GetSignaturesForAddressRequest.json"); + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri, + }; + + var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); + var result = sut.GetSignaturesForAddress("4Rf9mGD7FeYknun5JczX5nGLTfQuS1GRjNVfkEMKE92b", limit: 3); + + Assert.AreEqual(requestData, sentMessage); + Assert.IsNotNull(result.Result); + Assert.IsTrue(result.WasSuccessful); + Assert.AreEqual(3, result.Result.Count); + Assert.AreEqual(1616245823UL, result.Result[0].BlockTime); + Assert.AreEqual(68710495UL, result.Result[0].Slot); + Assert.AreEqual("5Jofwx5JcPT1dMsgo6DkyT6x61X5chS9K7hM7huGKAnUq8xxHwGKuDnnZmPGoapWVZcN4cPvQtGNCicnWZfPHowr", result.Result[0].Signature); + Assert.AreEqual(null, result.Result[0].Memo); + Assert.AreEqual(null, result.Result[0].Error); + + FinishTest(messageHandlerMock, TestnetUri); + } + + [TestMethod] + public void TestGetSignaturesForAddressUntil() + { + var responseData = File.ReadAllText("Resources/Http/Signatures/GetSignaturesForAddressUntilResponse.json"); + var requestData = File.ReadAllText("Resources/Http/Signatures/GetSignaturesForAddressUntilRequest.json"); + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri, + }; + + var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); + var result = sut.GetSignaturesForAddress( + "Vote111111111111111111111111111111111111111", + 1, until: "Vote111111111111111111111111111111111111111"); + + Assert.AreEqual(requestData, sentMessage); + Assert.IsNotNull(result.Result); + Assert.IsTrue(result.WasSuccessful); + Assert.AreEqual(1, result.Result.Count); + Assert.AreEqual(null, result.Result[0].BlockTime); + Assert.AreEqual(114UL, result.Result[0].Slot); + Assert.AreEqual("5h6xBEauJ3PK6SWCZ1PGjBvj8vDdWG3KpwATGy1ARAXFSDwt8GFXM7W5Ncn16wmqokgpiKRLuS83KUxyZyv2sUYv", result.Result[0].Signature); + Assert.AreEqual(null, result.Result[0].Memo); + Assert.AreEqual(null, result.Result[0].Error); + + FinishTest(messageHandlerMock, TestnetUri); + } + + [TestMethod] + public void TestGetSignaturesForAddressBefore() + { + var responseData = File.ReadAllText("Resources/Http/Signatures/GetSignaturesForAddressBeforeResponse.json"); + var requestData = File.ReadAllText("Resources/Http/Signatures/GetSignaturesForAddressBeforeRequest.json"); + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri, + }; + + var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); + var result = sut.GetSignaturesForAddress( + "Vote111111111111111111111111111111111111111", + 1, before: "Vote111111111111111111111111111111111111111"); + + Assert.AreEqual(requestData, sentMessage); + Assert.IsNotNull(result.Result); + Assert.IsTrue(result.WasSuccessful); + Assert.AreEqual(1, result.Result.Count); + Assert.AreEqual(null, result.Result[0].BlockTime); + Assert.AreEqual(114UL, result.Result[0].Slot); + Assert.AreEqual("5h6xBEauJ3PK6SWCZ1PGjBvj8vDdWG3KpwATGy1ARAXFSDwt8GFXM7W5Ncn16wmqokgpiKRLuS83KUxyZyv2sUYv", result.Result[0].Signature); + Assert.AreEqual(null, result.Result[0].Memo); + Assert.AreEqual(null, result.Result[0].Error); + + FinishTest(messageHandlerMock, TestnetUri); + } + + [TestMethod] + public void TestGetSignatureStatuses() + { + var responseData = File.ReadAllText("Resources/Http/Signatures/GetSignatureStatusesResponse.json"); + var requestData = File.ReadAllText("Resources/Http/Signatures/GetSignatureStatusesRequest.json"); + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri, + }; + + var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); + var result = sut.GetSignatureStatuses( + new List + { + "5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW", + "5j7s6NiJS3JAkvgkoc18WVAsiSaci2pxB2A6ueCJP4tprA2TFg9wSyTLeYouxPBJEMzJinENTkpA52YStRW5Dia7" + }); + + Assert.AreEqual(requestData, sentMessage); + Assert.IsNotNull(result.Result); + Assert.IsTrue(result.WasSuccessful); + Assert.AreEqual(82UL, result.Result.Context.Slot); + Assert.AreEqual(2, result.Result.Value.Count); + Assert.AreEqual(null, result.Result.Value[1]); + Assert.AreEqual(72UL, result.Result.Value[0].Slot); + Assert.AreEqual(10UL, result.Result.Value[0].Confirmations); + Assert.AreEqual("confirmed", result.Result.Value[0].ConfirmationStatus); + + FinishTest(messageHandlerMock, TestnetUri); + } + + [TestMethod] + public void TestGetSignatureStatusesWithHistory() + { + var responseData = File.ReadAllText("Resources/Http/Signatures/GetSignatureStatusesWithHistoryResponse.json"); + var requestData = File.ReadAllText("Resources/Http/Signatures/GetSignatureStatusesWithHistoryRequest.json"); + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri, + }; + + var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); + var result = sut.GetSignatureStatuses( + new List + { + "5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW", + "5j7s6NiJS3JAkvgkoc18WVAsiSaci2pxB2A6ueCJP4tprA2TFg9wSyTLeYouxPBJEMzJinENTkpA52YStRW5Dia7" + }, true); + + Assert.AreEqual(requestData, sentMessage); + Assert.IsNotNull(result.Result); + Assert.IsTrue(result.WasSuccessful); + Assert.AreEqual(82UL, result.Result.Context.Slot); + Assert.AreEqual(2, result.Result.Value.Count); + Assert.AreEqual(null, result.Result.Value[1]); + Assert.AreEqual(48UL, result.Result.Value[0].Slot); + Assert.AreEqual(null, result.Result.Value[0].Confirmations); + Assert.AreEqual("finalized", result.Result.Value[0].ConfirmationStatus); + + FinishTest(messageHandlerMock, TestnetUri); + } + + [TestMethod] + public void TestGetSnapshotSlot() + { + var responseData = File.ReadAllText("Resources/Http/GetSnapshotSlotResponse.json"); + var requestData = File.ReadAllText("Resources/Http/GetSnapshotSlotRequest.json"); + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri, + }; + + var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); + var result = sut.GetSnapshotSlot(); + + Assert.AreEqual(requestData, sentMessage); + Assert.IsNotNull(result.Result); + Assert.IsTrue(result.WasSuccessful); + Assert.AreEqual(100UL, result.Result); + + FinishTest(messageHandlerMock, TestnetUri); + } + + [TestMethod] + public void TestGetSupply() + { + var responseData = File.ReadAllText("Resources/Http/GetSupplyResponse.json"); + var requestData = File.ReadAllText("Resources/Http/GetSupplyRequest.json"); + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri, + }; + + var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); + var result = sut.GetSupply(); + + Assert.AreEqual(requestData, sentMessage); + Assert.IsNotNull(result.Result); + Assert.IsTrue(result.WasSuccessful); + Assert.AreEqual(79266564UL, result.Result.Context.Slot); + Assert.AreEqual(1359481823340465122UL, result.Result.Value.Circulating); + Assert.AreEqual(122260000000UL, result.Result.Value.NonCirculating); + Assert.AreEqual(16, result.Result.Value.NonCirculatingAccounts.Count); + + FinishTest(messageHandlerMock, TestnetUri); + } + + [TestMethod] + public void TestGetStakeActivation() + { + var responseData = File.ReadAllText("Resources/Http/GetStakeActivationResponse.json"); + var requestData = File.ReadAllText("Resources/Http/GetStakeActivationRequest.json"); + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri, + }; + + var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); + var result = sut.GetStakeActivation("CYRJWqiSjLitBAcRxPvWpgX3s5TvmN2SuRY3eEYypFvT"); + + Assert.AreEqual(requestData, sentMessage); + Assert.IsNotNull(result.Result); + Assert.IsTrue(result.WasSuccessful); + Assert.AreEqual(197717120UL, result.Result.Active); + Assert.AreEqual(0UL, result.Result.Inactive); + Assert.AreEqual("active", result.Result.State); + + FinishTest(messageHandlerMock, TestnetUri); + } + + [TestMethod] + public void TestGetStakeActivationWithEpoch() + { + var responseData = File.ReadAllText("Resources/Http/GetStakeActivationWithEpochResponse.json"); + var requestData = File.ReadAllText("Resources/Http/GetStakeActivationWithEpochRequest.json"); + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri, + }; + + var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); + var result = sut.GetStakeActivation("CYRJWqiSjLitBAcRxPvWpgX3s5TvmN2SuRY3eEYypFvT", 4); + + Assert.AreEqual(requestData, sentMessage); + Assert.IsNotNull(result.Result); + Assert.IsTrue(result.WasSuccessful); + Assert.AreEqual(124429280UL, result.Result.Active); + Assert.AreEqual(73287840UL, result.Result.Inactive); + Assert.AreEqual("activating", result.Result.State); + + FinishTest(messageHandlerMock, TestnetUri); + } + + [TestMethod] + public void TestGetTokenSupply() + { + var responseData = File.ReadAllText("Resources/Http/GetTokenSupplyResponse.json"); + var requestData = File.ReadAllText("Resources/Http/GetTokenSupplyRequest.json"); + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri, + }; + + var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); + var result = sut.GetTokenSupply("7ugkvt26sFjMdiFQFP5AQX8m8UkxWaW7rk2nBk4R6Gf2"); + + Assert.AreEqual(requestData, sentMessage); + Assert.IsNotNull(result.Result); + Assert.IsTrue(result.WasSuccessful); + Assert.AreEqual(79266576UL, result.Result.Context.Slot); + Assert.AreEqual("1000", result.Result.Value.Amount); + Assert.AreEqual(2, result.Result.Value.Decimals); + Assert.AreEqual("10", result.Result.Value.UiAmountString); + + FinishTest(messageHandlerMock, TestnetUri); + } + + [TestMethod] + [ExpectedException(typeof(AggregateException))] + public void TestGetTokenAccountsByOwnerException() + { + var httpClient = new HttpClient + { + BaseAddress = TestnetUri, + }; + + var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); + _ = sut.GetTokenAccountsByOwner("9we6kjtbcZ2vy3GSLLsZTEhbAqXPTRvEyoxa8wxSqKp5"); + } + + [TestMethod] + public void TestGetTokenAccountsByOwner() + { + var responseData = File.ReadAllText("Resources/Http/GetTokenAccountsByOwnerResponse.json"); + var requestData = File.ReadAllText("Resources/Http/GetTokenAccountsByOwnerRequest.json"); + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri, + }; + + var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); + var result = sut.GetTokenAccountsByOwner( + "9we6kjtbcZ2vy3GSLLsZTEhbAqXPTRvEyoxa8wxSqKp5", + tokenProgramId: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"); + + Assert.AreEqual(requestData, sentMessage); + Assert.IsNotNull(result.Result); + Assert.IsTrue(result.WasSuccessful); + Assert.AreEqual(79200468UL, result.Result.Context.Slot); + Assert.AreEqual(7, result.Result.Value.Count); + + FinishTest(messageHandlerMock, TestnetUri); + } + + [TestMethod] + public void TestGetTokenAccountsByDelegate() + { + var responseData = File.ReadAllText("Resources/Http/GetTokenAccountsByDelegateResponse.json"); + var requestData = File.ReadAllText("Resources/Http/GetTokenAccountsByDelegateRequest.json"); + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri, + }; + + var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); + var result = sut.GetTokenAccountsByDelegate( + "4Nd1mBQtrMJVYVfKf2PJy9NZUZdTAsp7D4xWLs4gDB4T", + tokenProgramId: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"); + + Assert.AreEqual(requestData, sentMessage); + Assert.IsNotNull(result.Result); + Assert.IsTrue(result.WasSuccessful); + Assert.AreEqual(1114UL, result.Result.Context.Slot); + Assert.AreEqual(1, result.Result.Value.Count); + Assert.AreEqual("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", result.Result.Value[0].Account.Owner); + Assert.AreEqual(false, result.Result.Value[0].Account.Executable); + Assert.AreEqual(4UL, result.Result.Value[0].Account.RentEpoch); + Assert.AreEqual(1726080UL, result.Result.Value[0].Account.Lamports); + Assert.AreEqual("4Nd1mBQtrMJVYVfKf2PJy9NZUZdTAsp7D4xWLs4gDB4T", + result.Result.Value[0].Account.Data.Parsed.Info.Delegate); + Assert.AreEqual(1UL, result.Result.Value[0].Account.Data.Parsed.Info.DelegatedAmount); + + FinishTest(messageHandlerMock, TestnetUri); + } + + [TestMethod] + public void TestGetTokenAccountBalance() + { + var responseData = File.ReadAllText("Resources/Http/GetTokenAccountBalanceResponse.json"); + var requestData = File.ReadAllText("Resources/Http/GetTokenAccountBalanceRequest.json"); + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri, + }; + + var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); + var result = sut.GetTokenAccountBalance("7247amxcSBamBSKZJrqbj373CiJSa1v21cRav56C3WfZ"); + + Assert.AreEqual(requestData, sentMessage); + Assert.IsNotNull(result.Result); + Assert.IsTrue(result.WasSuccessful); + Assert.AreEqual(79207643UL, result.Result.Context.Slot); + Assert.AreEqual("1000", result.Result.Value.Amount); + Assert.AreEqual(2, result.Result.Value.Decimals); + Assert.AreEqual("10", result.Result.Value.UiAmountString); + + FinishTest(messageHandlerMock, TestnetUri); + } + + [TestMethod] + public void TestGetTokenLargestAccounts() + { + var responseData = File.ReadAllText("Resources/Http/GetTokenLargestAccountsResponse.json"); + var requestData = File.ReadAllText("Resources/Http/GetTokenLargestAccountsRequest.json"); + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri, + }; + + var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); + var result = sut.GetTokenLargestAccounts("7ugkvt26sFjMdiFQFP5AQX8m8UkxWaW7rk2nBk4R6Gf2"); + + Assert.AreEqual(requestData, sentMessage); + Assert.IsNotNull(result.Result); + Assert.IsTrue(result.WasSuccessful); + Assert.AreEqual(79207653UL, result.Result.Context.Slot); + Assert.AreEqual(1, result.Result.Value.Count); + Assert.AreEqual("7247amxcSBamBSKZJrqbj373CiJSa1v21cRav56C3WfZ", result.Result.Value[0].Address); + Assert.AreEqual("1000", result.Result.Value[0].Amount); + Assert.AreEqual(2, result.Result.Value[0].Decimals); + Assert.AreEqual("10", result.Result.Value[0].UiAmountString); + + FinishTest(messageHandlerMock, TestnetUri); + } + + [TestMethod] + public void TestGetMinimumLedgerSlot() + { + var responseData = File.ReadAllText("Resources/Http/GetMinimumLedgerSlotResponse.json"); + var requestData = File.ReadAllText("Resources/Http/GetMinimumLedgerSlotRequest.json"); + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri, + }; + + var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); + var result = sut.GetMinimumLedgerSlot(); + + Assert.AreEqual(requestData, sentMessage); + Assert.IsNotNull(result.Result); + Assert.IsTrue(result.WasSuccessful); + Assert.AreEqual(78969229UL, result.Result); + + FinishTest(messageHandlerMock, TestnetUri); + } + + [TestMethod] + public void TestGetVersion() + { + var responseData = File.ReadAllText("Resources/Http/GetVersionResponse.json"); + var requestData = File.ReadAllText("Resources/Http/GetVersionRequest.json"); + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri, + }; + + var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); + var result = sut.GetVersion(); + + Assert.AreEqual(requestData, sentMessage); + Assert.IsNotNull(result.Result); + Assert.IsTrue(result.WasSuccessful); + Assert.AreEqual(1082270801UL, result.Result.FeatureSet); + Assert.AreEqual("1.6.11", result.Result.SolanaCore); + + FinishTest(messageHandlerMock, TestnetUri); + } + + [TestMethod] + public void TestGetVoteAccounts() + { + var responseData = File.ReadAllText("Resources/Http/GetVoteAccountsResponse.json"); + var requestData = File.ReadAllText("Resources/Http/GetVoteAccountsRequest.json"); + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri, + }; + + var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); + var result = sut.GetVoteAccounts(); + + Assert.AreEqual(requestData, sentMessage); + Assert.IsNotNull(result.Result); + Assert.IsTrue(result.WasSuccessful); + Assert.AreEqual(1, result.Result.Current.Length); + Assert.AreEqual(1, result.Result.Delinquent.Length); + + Assert.AreEqual(81274518UL, result.Result.Current[0].RootSlot); + Assert.AreEqual("3ZT31jkAGhUaw8jsy4bTknwBMP8i4Eueh52By4zXcsVw", result.Result.Current[0].VotePublicKey); + Assert.AreEqual("B97CCUW3AEZFGy6uUg6zUdnNYvnVq5VG8PUtb2HayTDD", result.Result.Current[0].NodePublicKey); + Assert.AreEqual(42UL, result.Result.Current[0].ActivatedStake); + Assert.AreEqual(0, result.Result.Current[0].Commission); + Assert.AreEqual(147UL, result.Result.Current[0].LastVote); + Assert.AreEqual(true, result.Result.Current[0].EpochVoteAccount); + Assert.AreEqual(2, result.Result.Current[0].EpochCredits.Length); + + Assert.AreEqual(1234UL, result.Result.Delinquent[0].RootSlot); + Assert.AreEqual("CmgCk4aMS7KW1SHX3s9K5tBJ6Yng2LBaC8MFov4wx9sm", result.Result.Delinquent[0].VotePublicKey); + Assert.AreEqual("6ZPxeQaDo4bkZLRsdNrCzchNQr5LN9QMc9sipXv9Kw8f", result.Result.Delinquent[0].NodePublicKey); + Assert.AreEqual(0UL, result.Result.Delinquent[0].ActivatedStake); + Assert.AreEqual(false, result.Result.Delinquent[0].EpochVoteAccount); + Assert.AreEqual(127UL, result.Result.Delinquent[0].Commission); + Assert.AreEqual(0UL, result.Result.Delinquent[0].LastVote); + Assert.AreEqual(0, result.Result.Delinquent[0].EpochCredits.Length); + + FinishTest(messageHandlerMock, TestnetUri); + } + + [TestMethod] + public void TestGetTransactionCount() + { + var responseData = File.ReadAllText("Resources/Http/GetTransactionCountResponse.json"); + var requestData = File.ReadAllText("Resources/Http/GetTransactionCountRequest.json"); + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri, + }; + + var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); + var result = sut.GetTransactionCount(); + + Assert.AreEqual(requestData, sentMessage); + Assert.IsNotNull(result.Result); + Assert.IsTrue(result.WasSuccessful); + Assert.AreEqual(23632393337UL, result.Result); + + FinishTest(messageHandlerMock, TestnetUri); + } + + [TestMethod] + public void TestSendTransaction() + { + var responseData = File.ReadAllText("Resources/Http/SendTransactionResponse.json"); + var requestData = File.ReadAllText("Resources/Http/SendTransactionRequest.json"); + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri, + }; + + var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); + var txData = "ASIhFkj3HRTDLiPxrxudL7eXCQ3DKrBB6Go/pn0sHWYIYgIHWYu2jZjbDXQseCEu73Li53BP7AEt8lCwKz" + + "X5awcBAAIER2mrlyBLqD\u002Bwyu4X94aPHgdOUhWBoNidlDedqmW3F7J7rHLZwOnCKOnqrRmjOO1w2JcV0XhP" + + "LlWiw5thiFgQQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABUpTUPhdyILWFKVWcniKKW3fHqur0KY" + + "GeIhJMvTu9qCKNNRNmSFNMnUzw5\u002BFDszWV6YvuvspBr0qlIoAdeg67wICAgABDAIAAACAlpgAAAAAAAMBA" + + "BVIZWxsbyBmcm9tIFNvbC5OZXQgOik="; + + var result = sut.SendTransaction(txData); + + Assert.AreEqual(requestData, sentMessage); + Assert.IsNotNull(result.Result); + Assert.IsTrue(result.WasSuccessful); + Assert.AreEqual("gaSFQXFqbYQypZdMFZy4Fe7uB2VFDEo4sGDypyrVxFgzZqc5MqWnRWTT9hXamcrFRcsiiH15vWii5ACSsyNScbp", + result.Result); + + FinishTest(messageHandlerMock, TestnetUri); + } + + [TestMethod] + public void TestSimulateTransaction() + { + var responseData = File.ReadAllText("Resources/Http/SimulateTransactionResponse.json"); + var requestData = File.ReadAllText("Resources/Http/SimulateTransactionRequest.json"); + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri, + }; + + var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); + var txData = "ASIhFkj3HRTDLiPxrxudL7eXCQ3DKrBB6Go/pn0sHWYIYgIHWYu2jZjbDXQseCEu73Li53BP7AEt8lCwKz" + + "X5awcBAAIER2mrlyBLqD\u002Bwyu4X94aPHgdOUhWBoNidlDedqmW3F7J7rHLZwOnCKOnqrRmjOO1w2JcV0XhP" + + "LlWiw5thiFgQQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABUpTUPhdyILWFKVWcniKKW3fHqur0KY" + + "GeIhJMvTu9qCKNNRNmSFNMnUzw5\u002BFDszWV6YvuvspBr0qlIoAdeg67wICAgABDAIAAACAlpgAAAAAAAMBA" + + "BVIZWxsbyBmcm9tIFNvbC5OZXQgOik="; + + var result = sut.SimulateTransaction(txData); + + Assert.AreEqual(requestData, sentMessage); + Assert.IsNotNull(result.Result.Value); + Assert.AreEqual(79206888UL, result.Result.Context.Slot); + Assert.AreEqual(null, result.Result.Value.Error); + Assert.AreEqual(5, result.Result.Value.Logs.Length); + + FinishTest(messageHandlerMock, TestnetUri); + } + + [TestMethod] + public void TestSimulateTransactionInsufficientLamports() + { + var responseData = File.ReadAllText("Resources/Http/SimulateTransactionInsufficientLamportsResponse.json"); + var requestData = File.ReadAllText("Resources/Http/SimulateTransactionInsufficientLamportsRequest.json"); + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri, + }; + + var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); + var txData = "ARymmnVB6PB0x//jV2vsTFFdeOkzD0FFoQq6P\u002BwzGKlMD\u002BXLb/hWnOebNaYlg/" + + "\u002Bj6jdm9Fe2Sba/ACnvcv9KIA4BAAIEUy4zulRg8z2yKITZaNwcnq6G6aH8D0ITae862qbJ" + + "\u002B3eE3M6r5DRwldquwlqOuXDDOWZagXmbHnAU3w5Dg44kogAAAAAAAAAAAAAAAAAAAAAAAA" + + "AAAAAAAAAAAAAAAAAABUpTUPhdyILWFKVWcniKKW3fHqur0KYGeIhJMvTu9qBann0itTd6uxx69h" + + "ION5Js4E4drRP8CWwoLTdorAFUqAICAgABDAIAAACAlpgAAAAAAAMBABVIZWxsbyBmcm9tIFNvbC5OZXQgOik="; + + var result = sut.SimulateTransaction(txData); + + Assert.AreEqual(requestData, sentMessage); + Assert.IsNotNull(result.Result.Value); + Assert.AreEqual(79203980UL, result.Result.Context.Slot); + Assert.AreEqual(3, result.Result.Value.Logs.Length); + Assert.IsNotNull(result.Result.Value.Error); + Assert.AreEqual(TransactionErrorType.InstructionError, result.Result.Value.Error.Type); + Assert.IsNotNull(result.Result.Value.Error.InstructionError); + Assert.AreEqual(InstructionErrorType.Custom, result.Result.Value.Error.InstructionError.Type); + Assert.AreEqual(1, result.Result.Value.Error.InstructionError.CustomError); + FinishTest(messageHandlerMock, TestnetUri); + } + + + [TestMethod] + public void TestGetHealth_HealthyResponse() + { + + var responseData = File.ReadAllText("Resources/Http/Health/GetHealthHealthyResponse.json"); + var requestData = File.ReadAllText("Resources/Http/Health/GetHealthRequest.json"); + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri + }; + var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); + + var res = sut.GetHealth(); + + Assert.AreEqual(requestData, sentMessage); + Assert.AreEqual("ok", res.Result); + + FinishTest(messageHandlerMock, TestnetUri); + } + + [TestMethod] + public void TestGetHealth_UnhealthyResponse() + { + + var responseData = File.ReadAllText("Resources/Http/Health/GetHealthUnhealthyResponse.json"); + var requestData = File.ReadAllText("Resources/Http/Health/GetHealthRequest.json"); + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri + }; + var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); + + var res = sut.GetHealth(); + + Assert.AreEqual(requestData, sentMessage); + Assert.IsNull(res.Result); + Assert.IsTrue(res.WasHttpRequestSuccessful); + Assert.IsFalse(res.WasRequestSuccessfullyHandled); + Assert.AreEqual(-32005, res.ServerErrorCode); + Assert.AreEqual("Node is behind by 42 slots", res.Reason); + + FinishTest(messageHandlerMock, TestnetUri); + } + + [TestMethod] + public void TestGetLeaderSchedule_SlotArgsRequest() + { + + var responseData = File.ReadAllText("Resources/Http/LeaderSchedule/GetLeaderScheduleResponse.json"); + var requestData = File.ReadAllText("Resources/Http/LeaderSchedule/GetLeaderScheduleSlotArgsRequest.json"); + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri + }; + var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); + + var res = sut.GetLeaderSchedule(79700000); + + Assert.AreEqual(requestData, sentMessage); + Assert.IsNotNull(res.Result); + Assert.IsTrue(res.WasSuccessful); + + Assert.AreEqual(2, res.Result.Count); + Assert.IsTrue(res.Result.ContainsKey("4Qkev8aNZcqFNSRhQzwyLMFSsi94jHqE8WNVTJzTP99F")); + + Assert.AreEqual(7, res.Result["4Qkev8aNZcqFNSRhQzwyLMFSsi94jHqE8WNVTJzTP99F"].Count); + Assert.AreEqual(0UL, res.Result["4Qkev8aNZcqFNSRhQzwyLMFSsi94jHqE8WNVTJzTP99F"][0]); + + FinishTest(messageHandlerMock, TestnetUri); + } + + [TestMethod] + public void TestGetLeaderSchedule_IdentityArgsRequest() + { + + var responseData = File.ReadAllText("Resources/Http/LeaderSchedule/GetLeaderScheduleResponse.json"); + var requestData = File.ReadAllText("Resources/Http/LeaderSchedule/GetLeaderScheduleIdentityArgsRequest.json"); + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri + }; + var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); + + var res = sut.GetLeaderSchedule(identity: "Bbe9EKucmRtJr2J4dd5Eb5ybQmY7Fm7jYxKXxmmkLFsu"); + + Assert.AreEqual(requestData, sentMessage); + Assert.IsNotNull(res.Result); + Assert.IsTrue(res.WasSuccessful); + + Assert.AreEqual(2, res.Result.Count); + Assert.IsTrue(res.Result.ContainsKey("4Qkev8aNZcqFNSRhQzwyLMFSsi94jHqE8WNVTJzTP99F")); + + Assert.AreEqual(7, res.Result["4Qkev8aNZcqFNSRhQzwyLMFSsi94jHqE8WNVTJzTP99F"].Count); + Assert.AreEqual(0UL, res.Result["4Qkev8aNZcqFNSRhQzwyLMFSsi94jHqE8WNVTJzTP99F"][0]); + + FinishTest(messageHandlerMock, TestnetUri); + } + + [TestMethod] + public void TestGetLeaderSchedule_SlotIdentityArgsRequest() + { + + var responseData = File.ReadAllText("Resources/Http/LeaderSchedule/GetLeaderScheduleResponse.json"); + var requestData = File.ReadAllText("Resources/Http/LeaderSchedule/GetLeaderScheduleSlotIdentityArgsRequest.json"); + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri + }; + var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); + + var res = sut.GetLeaderSchedule(79700000, "Bbe9EKucmRtJr2J4dd5Eb5ybQmY7Fm7jYxKXxmmkLFsu"); + + Assert.AreEqual(requestData, sentMessage); + Assert.IsNotNull(res.Result); + Assert.IsTrue(res.WasSuccessful); + + Assert.AreEqual(2, res.Result.Count); + Assert.IsTrue(res.Result.ContainsKey("4Qkev8aNZcqFNSRhQzwyLMFSsi94jHqE8WNVTJzTP99F")); + + Assert.AreEqual(7, res.Result["4Qkev8aNZcqFNSRhQzwyLMFSsi94jHqE8WNVTJzTP99F"].Count); + Assert.AreEqual(0UL, res.Result["4Qkev8aNZcqFNSRhQzwyLMFSsi94jHqE8WNVTJzTP99F"][0]); + + FinishTest(messageHandlerMock, TestnetUri); + } + + [TestMethod] + public void TestGetLeaderSchedule_NoArgsRequest() + { + + var responseData = File.ReadAllText("Resources/Http/LeaderSchedule/GetLeaderScheduleResponse.json"); + var requestData = File.ReadAllText("Resources/Http/LeaderSchedule/GetLeaderScheduleNoArgsRequest.json"); + var sentMessage = string.Empty; + var messageHandlerMock = SetupTest( + (s => sentMessage = s), responseData); + + var httpClient = new HttpClient(messageHandlerMock.Object) + { + BaseAddress = TestnetUri + }; + var sut = new SolanaRpcClient(TestnetUrl, null, httpClient); + + var res = sut.GetLeaderSchedule(); + + Assert.AreEqual(requestData, sentMessage); + Assert.IsNotNull(res.Result); + Assert.IsTrue(res.WasSuccessful); + + Assert.AreEqual(2, res.Result.Count); + Assert.IsTrue(res.Result.ContainsKey("4Qkev8aNZcqFNSRhQzwyLMFSsi94jHqE8WNVTJzTP99F")); + + Assert.AreEqual(7, res.Result["4Qkev8aNZcqFNSRhQzwyLMFSsi94jHqE8WNVTJzTP99F"].Count); + Assert.AreEqual(0UL, res.Result["4Qkev8aNZcqFNSRhQzwyLMFSsi94jHqE8WNVTJzTP99F"][0]); + + FinishTest(messageHandlerMock, TestnetUri); + } + } } \ No newline at end of file diff --git a/test/Solnet.Rpc.Test/SolanaStreamingClientTest.cs b/test/Solnet.Rpc.Test/SolanaStreamingClientTest.cs index 5fd09c78..56634096 100644 --- a/test/Solnet.Rpc.Test/SolanaStreamingClientTest.cs +++ b/test/Solnet.Rpc.Test/SolanaStreamingClientTest.cs @@ -1,512 +1,512 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; -using Solnet.Rpc.Core.Sockets; -using Solnet.Rpc.Messages; -using Solnet.Rpc.Models; -using System; -using System.IO; -using System.Net.WebSockets; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace Solnet.Rpc.Test -{ - [TestClass] - public class SolanaStreamingClientTest - { - private Mock _socketMock; - private ManualResetEvent _notificationEvent; - private ManualResetEvent _subConfirmEvent; - private bool _isSubConfirmed; - private bool _hasNotified; - private bool _hasEnded; - private ValueTask _valueTaskConfirmation; - private ValueTask _valueTaskNotification; - private ValueTask _valueTaskEnd; - - private void SetupAction(out Action action, Action resultCaptureCallback, Action> sentPayloadCaptureCallback, byte[] subConfirmContent, byte[] notificationContents) - { - - var actionMock = new Mock>(); - actionMock.Setup(_ => _(It.IsAny(), It.IsAny())).Callback((_, notifValue) => - { - resultCaptureCallback(notifValue); - _notificationEvent.Set(); - }); - action = actionMock.Object; - - _valueTaskConfirmation = new ValueTask( - new ValueWebSocketReceiveResult(subConfirmContent.Length, WebSocketMessageType.Text, true)); - _valueTaskNotification = new ValueTask( - new ValueWebSocketReceiveResult(notificationContents.Length, WebSocketMessageType.Text, true)); - - _valueTaskEnd = new ValueTask( - new ValueWebSocketReceiveResult(0, WebSocketMessageType.Close, true)); - - _socketMock.Setup(_ => _.ConnectAsync(It.IsAny(), It.IsAny())) - .Callback(() => _socketMock.SetupGet(s => s.State).Returns(WebSocketState.Open)); - - _socketMock.Setup(_ => _.SendAsync(It.IsAny>(), WebSocketMessageType.Text, true, It.IsAny())) - .Callback, WebSocketMessageType, bool, CancellationToken>((mem, _, _, _) => sentPayloadCaptureCallback(mem)); - - _socketMock.Setup(_ => _.ReceiveAsync(It.IsAny>(), It.IsAny())). - Callback, CancellationToken>((mem, _) => - { - if (!_isSubConfirmed) - { - _subConfirmEvent.WaitOne(); - subConfirmContent.CopyTo(mem); - _isSubConfirmed = true; - } - else if (!_hasNotified) - { - notificationContents.CopyTo(mem); - _hasNotified = true; - - _socketMock.SetupGet(s => s.State).Returns(WebSocketState.Closed); - } - else if (!_hasEnded) - { - _hasEnded = true; - } - - }).Returns(() => _hasEnded ? _valueTaskEnd : _hasNotified ? _valueTaskNotification : _valueTaskConfirmation); - - } - - [TestInitialize] - public void SetupTest() - { - _socketMock = new Mock(); - _notificationEvent = new ManualResetEvent(false); - _subConfirmEvent = new ManualResetEvent(false); - _isSubConfirmed = false; - _hasNotified = false; - _hasEnded = false; - } - - [TestMethod] - public void SubscribeAccountInfoTest() - { - var expected = File.ReadAllText("Resources/AccountSubscribe.json"); - var subConfirmContent = File.ReadAllBytes("Resources/SubscribeConfirm.json"); - var notificationContents = File.ReadAllBytes("Resources/AccountSubscribeNotification.json"); - ResponseValue resultNotification = null; - var result = new ReadOnlyMemory(); - - SetupAction(out Action> action, - (x) => resultNotification = x, - (x) => result = x, - subConfirmContent, - notificationContents); - - var sut = new SolanaStreamingRpcClient("wss://api.mainnet-beta.solana.com/", null, _socketMock.Object); - - const string pubKey = "CM78CPUeXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNH12"; - - sut.Init().Wait(); - _ = sut.SubscribeAccountInfo(pubKey, action); - _subConfirmEvent.Set(); - - _socketMock.Verify(s => s.SendAsync(It.IsAny>(), - WebSocketMessageType.Text, - true, - It.IsAny())); - var res = Encoding.UTF8.GetString(result.Span); - Assert.AreEqual(expected, res); - - Assert.IsTrue(_notificationEvent.WaitOne()); - Assert.AreEqual(5199307UL, resultNotification.Context.Slot); - Assert.AreEqual("11111111111111111111111111111111", resultNotification.Value.Owner); - Assert.AreEqual(33594UL, resultNotification.Value.Lamports); - Assert.AreEqual(635UL, resultNotification.Value.RentEpoch); - Assert.AreEqual(false, resultNotification.Value.Executable); - } - - - [TestMethod] - public void UnsubscribeTest() - { - var subConfirmContent = File.ReadAllBytes("Resources/SubscribeConfirm.json"); - var unsubContents = File.ReadAllBytes("Resources/AccountSubUnsubscription.json"); - var unsubscribed = false; - - SetupAction(out Action> action, - _ => { }, - _ => { }, - subConfirmContent, - unsubContents); - - var sut = new SolanaStreamingRpcClient("wss://api.mainnet-beta.solana.com/", null, _socketMock.Object); - - const string pubKey = "CM78CPUeXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNH12"; - - sut.Init().Wait(); - var sub = sut.SubscribeAccountInfo(pubKey, action); - sub.SubscriptionChanged += (_, e) => - { - if (e.Status == SubscriptionStatus.Unsubscribed) - { - unsubscribed = true; - _notificationEvent.Set(); - } - }; - - _subConfirmEvent.Set(); - - sub.Unsubscribe(); - - Assert.IsTrue(_notificationEvent.WaitOne()); - Assert.IsTrue(unsubscribed); - } - - [TestMethod] - public void SubscribeLogsMentionTest() - { - var expected = File.ReadAllText("Resources/LogsSubscribeMention.json"); - var subConfirmContent = File.ReadAllBytes("Resources/SubscribeConfirm.json"); - ResponseValue resultNotification = null; - var result = new ReadOnlyMemory(); - - SetupAction(out Action> action, - (x) => resultNotification = x, - (x) => result = x, - subConfirmContent, - new byte[0]); - - var sut = new SolanaStreamingRpcClient("wss://api.mainnet-beta.solana.com/", null, _socketMock.Object); - - const string pubKey = "11111111111111111111111111111111"; - - sut.Init().Wait(); - _ = sut.SubscribeLogInfo(pubKey, action); - - _socketMock.Verify(s => s.SendAsync(It.IsAny>(), - WebSocketMessageType.Text, - true, - It.IsAny())); - var res = Encoding.UTF8.GetString(result.Span); - Assert.AreEqual(expected, res); - - } - - [TestMethod] - public void SubscribeLogsAllTest() - { - var expected = File.ReadAllText("Resources/LogsSubscribeAll.json"); - var subConfirmContent = File.ReadAllBytes("Resources/SubscribeConfirm.json"); - var notificationContents = File.ReadAllBytes("Resources/LogsSubscribeNotification.json"); - ResponseValue resultNotification = null; - var result = new ReadOnlyMemory(); - - SetupAction(out Action> action, - (x) => resultNotification = x, - (x) => result = x, - subConfirmContent, - notificationContents); - - var sut = new SolanaStreamingRpcClient("wss://api.mainnet-beta.solana.com/", null, _socketMock.Object); - - sut.Init().Wait(); - _ = sut.SubscribeLogInfo(Types.LogsSubscriptionType.All, action); - _subConfirmEvent.Set(); - - _socketMock.Verify(s => s.SendAsync(It.IsAny>(), - WebSocketMessageType.Text, - true, - It.IsAny())); - var res = Encoding.UTF8.GetString(result.Span); - Assert.AreEqual(expected, res); - - Assert.IsTrue(_notificationEvent.WaitOne()); - Assert.AreEqual(5208469UL, resultNotification.Context.Slot); - Assert.AreEqual("5h6xBEauJ3PK6SWCZ1PGjBvj8vDdWG3KpwATGy1ARAXFSDwt8GFXM7W5Ncn16wmqokgpiKRLuS83KUxyZyv2sUYv", resultNotification.Value.Signature); - Assert.IsNull(resultNotification.Value.Error); - Assert.AreEqual("BPF program 83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri success", resultNotification.Value.Logs[0]); - } - - - [TestMethod] - public void SubscribeProgramTest() - { - var expected = File.ReadAllText("Resources/ProgramSubscribe.json"); - var subConfirmContent = File.ReadAllBytes("Resources/SubscribeConfirm.json"); - var notificationContents = File.ReadAllBytes("Resources/ProgramSubscribeNotification.json"); - ResponseValue resultNotification = null; - var result = new ReadOnlyMemory(); - - SetupAction(out Action> action, - (x) => resultNotification = x, - (x) => result = x, - subConfirmContent, - notificationContents); - - var sut = new SolanaStreamingRpcClient("wss://api.mainnet-beta.solana.com/", null, _socketMock.Object); - - sut.Init().Wait(); - _ = sut.SubscribeProgram("11111111111111111111111111111111", action); - _subConfirmEvent.Set(); - - _socketMock.Verify(s => s.SendAsync(It.IsAny>(), - WebSocketMessageType.Text, - true, - It.IsAny())); - var res = Encoding.UTF8.GetString(result.Span); - Assert.AreEqual(expected, res); - - Assert.IsTrue(_notificationEvent.WaitOne()); - Assert.AreEqual(80854485UL, resultNotification.Context.Slot); - Assert.AreEqual("9FXD1NXrK6xFU8i4gLAgjj2iMEWTqJhSuQN8tQuDfm2e", resultNotification.Value.PublicKey); - Assert.AreEqual("11111111111111111111111111111111", resultNotification.Value.Account.Owner); - Assert.AreEqual(false, resultNotification.Value.Account.Executable); - Assert.AreEqual(187UL, resultNotification.Value.Account.RentEpoch); - Assert.AreEqual(458553192193UL, resultNotification.Value.Account.Lamports); - } - - [TestMethod] - public void SubscribeSlotTest() - { - var expected = File.ReadAllText("Resources/SlotSubscribe.json"); - var subConfirmContent = File.ReadAllBytes("Resources/SubscribeConfirm.json"); - var notificationContents = File.ReadAllBytes("Resources/SlotSubscribeNotification.json"); - SlotInfo resultNotification = null; - var result = new ReadOnlyMemory(); - - SetupAction(out Action action, - (x) => resultNotification = x, - (x) => result = x, - subConfirmContent, - notificationContents); - - var sut = new SolanaStreamingRpcClient("wss://api.mainnet-beta.solana.com/", null, _socketMock.Object); - - sut.Init().Wait(); - _ = sut.SubscribeSlotInfo(action); - _subConfirmEvent.Set(); - - _socketMock.Verify(s => s.SendAsync(It.IsAny>(), - WebSocketMessageType.Text, - true, - It.IsAny())); - var res = Encoding.UTF8.GetString(result.Span); - Assert.AreEqual(expected, res); - - Assert.IsTrue(_notificationEvent.WaitOne()); - Assert.AreEqual(75, resultNotification.Parent); - Assert.AreEqual(44, resultNotification.Root); - Assert.AreEqual(76, resultNotification.Slot); - } - - - [TestMethod] - public void SubscribeRootTest() - { - var expected = File.ReadAllText("Resources/RootSubscribe.json"); - var subConfirmContent = File.ReadAllBytes("Resources/SubscribeConfirm.json"); - var notificationContents = File.ReadAllBytes("Resources/RootSubscribeNotification.json"); - int resultNotification = 0; - var result = new ReadOnlyMemory(); - - SetupAction(out Action action, - (x) => resultNotification = x, - (x) => result = x, - subConfirmContent, - notificationContents); - - var sut = new SolanaStreamingRpcClient("wss://api.mainnet-beta.solana.com/", null, _socketMock.Object); - - sut.Init().Wait(); - var sub = sut.SubscribeRoot(action); - _subConfirmEvent.Set(); - - _socketMock.Verify(s => s.SendAsync(It.IsAny>(), - WebSocketMessageType.Text, - true, - It.IsAny())); - var res = Encoding.UTF8.GetString(result.Span); - Assert.AreEqual(expected, res); - - Assert.IsTrue(_notificationEvent.WaitOne()); - Assert.AreEqual(42, resultNotification); - } - - - - [TestMethod] - public void SubscribeSignatureTest() - { - var expected = File.ReadAllText("Resources/SignatureSubscribe.json"); - var subConfirmContent = File.ReadAllBytes("Resources/SubscribeConfirm.json"); - var notificationContents = File.ReadAllBytes("Resources/SignatureSubscribeNotification.json"); - ResponseValue resultNotification = null; - AutoResetEvent subscriptionEvent = new AutoResetEvent(false); - var result = new ReadOnlyMemory(); - - SetupAction(out Action> action, - (x) => resultNotification = x, - (x) => result = x, - subConfirmContent, - notificationContents); - - var sut = new SolanaStreamingRpcClient("wss://api.mainnet-beta.solana.com/", null, _socketMock.Object); - - SubscriptionEvent evt = null; - - - sut.Init().Wait(); - var sub = sut.SubscribeSignature("4orRpuqStpJDvcpBy3vDSV4TDTGNbefmqYUnG2yVnKwjnLFqCwY4h5cBTAKakKek4inuxHF71LuscBS1vwSLtWcx", action); - sub.SubscriptionChanged += (s, e) => - { - evt = e; - if (e.Status == SubscriptionStatus.Unsubscribed) - subscriptionEvent.Set(); - }; - _subConfirmEvent.Set(); - - _socketMock.Verify(s => s.SendAsync(It.IsAny>(), - WebSocketMessageType.Text, - true, - It.IsAny())); - var res = Encoding.UTF8.GetString(result.Span); - Assert.AreEqual(expected, res); - - Assert.IsTrue(_notificationEvent.WaitOne()); - Assert.AreEqual("dummy error", resultNotification.Value.Error); - - Assert.IsTrue(subscriptionEvent.WaitOne()); - Assert.AreEqual(SubscriptionStatus.Unsubscribed, evt.Status); - Assert.AreEqual(SubscriptionStatus.Unsubscribed, sub.State); - } - - - [TestMethod] - public void SubscribeBadAccountTest() - { - var expected = File.ReadAllText("Resources/BadAccountSubscribe.json"); - var subConfirmContent = File.ReadAllBytes("Resources/BadAccountSubscribeResult.json"); - var result = new ReadOnlyMemory(); - AutoResetEvent subscriptionEvent = new AutoResetEvent(false); - SetupAction(out Action> action, - _ => { }, - (x) => result = x, - subConfirmContent, - new byte[0]); - - var sut = new SolanaStreamingRpcClient("wss://api.mainnet-beta.solana.com/", null, _socketMock.Object); - - const string pubKey = "invalidkey1"; - - sut.Init().Wait(); - var sub = sut.SubscribeAccountInfo(pubKey, action); - SubscriptionEvent subEvent = null; - sub.SubscriptionChanged += (sub, evt) => - { - subEvent = evt; - subscriptionEvent.Set(); - }; - - _subConfirmEvent.Set(); - - _socketMock.Verify(s => s.SendAsync(It.IsAny>(), - WebSocketMessageType.Text, - true, - It.IsAny())); - var res = Encoding.UTF8.GetString(result.Span); - Assert.AreEqual(expected, res); - - Assert.IsTrue(subscriptionEvent.WaitOne()); - Assert.AreEqual("-32602", subEvent.Code); - Assert.AreEqual(SubscriptionStatus.ErrorSubscribing, subEvent.Status); - Assert.AreEqual("Invalid Request: Invalid pubkey provided", subEvent.Error); - - Assert.AreEqual(SubscriptionStatus.ErrorSubscribing, sub.State); - } - - - - [TestMethod] - public void SubscribeAccountBigPayloadTest() - { - var expected = File.ReadAllText("Resources/BigAccountSubscribe.json"); - var subConfirmContent = File.ReadAllBytes("Resources/SubscribeConfirm.json"); - var notifContent = File.ReadAllBytes("Resources/BigAccountNotificationPayload.json"); - var expectedDataContent = File.ReadAllText("Resources/BigAccountNotificationPayloadData.txt"); - var result = new ReadOnlyMemory(); - - AutoResetEvent signal = new AutoResetEvent(false); - int currentMessageIdx = 0; - //confirm + bigpayload divided in two read steps + empty payload - var payloads = new Memory[] - { - new Memory(subConfirmContent), - new Memory(notifContent, 0, 32768), - new Memory(notifContent, 32768, notifContent.Length - 32768), - Memory.Empty - - }; - - AutoResetEvent subscriptionEvent = new AutoResetEvent(false); - ResponseValue notificationValue = null; - - var actionMock = new Mock>>(); - actionMock.Setup(_ => _(It.IsAny(), It.IsAny>())).Callback>((_, notifValue) => - { - notificationValue = notifValue; - _notificationEvent.Set(); - }); - - _socketMock.Setup(_ => _.ConnectAsync(It.IsAny(), It.IsAny())) - .Callback(() => _socketMock.SetupGet(s => s.State).Returns(WebSocketState.Open)); - - _socketMock.Setup(_ => _.SendAsync(It.IsAny>(), WebSocketMessageType.Text, true, It.IsAny())) - .Callback, WebSocketMessageType, bool, CancellationToken>((mem, _, _, _) => result = mem); - - _ = _socketMock.Setup(_ => _.ReceiveAsync(It.IsAny>(), It.IsAny())). - Callback, CancellationToken>((mem, _) => - { - if (currentMessageIdx == 0) - signal.WaitOne(); - payloads[currentMessageIdx++].CopyTo(mem); - }).Returns(() => new ValueTask( - new ValueWebSocketReceiveResult( - payloads[currentMessageIdx - 1].Length, - payloads[currentMessageIdx - 1].Length == 0 ? WebSocketMessageType.Close : WebSocketMessageType.Text, - currentMessageIdx == 2 ? false : true))); - - var sut = new SolanaStreamingRpcClient("wss://api.mainnet-beta.solana.com/", null, _socketMock.Object); - - const string pubKey = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"; - - sut.Init().Wait(); - var sub = sut.SubscribeAccountInfo(pubKey, actionMock.Object); - SubscriptionEvent subEvent = null; - sub.SubscriptionChanged += (sub, evt) => - { - subEvent = evt; - subscriptionEvent.Set(); - }; - - signal.Set(); - - _socketMock.Verify(s => s.SendAsync(It.IsAny>(), - WebSocketMessageType.Text, - true, - It.IsAny())); - - var res = Encoding.UTF8.GetString(result.Span); - Assert.AreEqual(expected, res); - - Assert.IsTrue(subscriptionEvent.WaitOne()); - Assert.IsTrue(string.IsNullOrEmpty(subEvent.Code)); - Assert.AreEqual(SubscriptionStatus.Subscribed, subEvent.Status); - Assert.IsTrue(string.IsNullOrEmpty(subEvent.Error)); - Assert.AreEqual(SubscriptionStatus.Subscribed, sub.State); - - Assert.IsTrue(_notificationEvent.WaitOne()); - - Assert.AreEqual(expectedDataContent, notificationValue.Value.Data[0]); - Assert.AreEqual("base64", notificationValue.Value.Data[1]); - } - } +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Solnet.Rpc.Core.Sockets; +using Solnet.Rpc.Messages; +using Solnet.Rpc.Models; +using System; +using System.IO; +using System.Net.WebSockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Solnet.Rpc.Test +{ + [TestClass] + public class SolanaStreamingClientTest + { + private Mock _socketMock; + private ManualResetEvent _notificationEvent; + private ManualResetEvent _subConfirmEvent; + private bool _isSubConfirmed; + private bool _hasNotified; + private bool _hasEnded; + private ValueTask _valueTaskConfirmation; + private ValueTask _valueTaskNotification; + private ValueTask _valueTaskEnd; + + private void SetupAction(out Action action, Action resultCaptureCallback, Action> sentPayloadCaptureCallback, byte[] subConfirmContent, byte[] notificationContents) + { + + var actionMock = new Mock>(); + actionMock.Setup(_ => _(It.IsAny(), It.IsAny())).Callback((_, notifValue) => + { + resultCaptureCallback(notifValue); + _notificationEvent.Set(); + }); + action = actionMock.Object; + + _valueTaskConfirmation = new ValueTask( + new ValueWebSocketReceiveResult(subConfirmContent.Length, WebSocketMessageType.Text, true)); + _valueTaskNotification = new ValueTask( + new ValueWebSocketReceiveResult(notificationContents.Length, WebSocketMessageType.Text, true)); + + _valueTaskEnd = new ValueTask( + new ValueWebSocketReceiveResult(0, WebSocketMessageType.Close, true)); + + _socketMock.Setup(_ => _.ConnectAsync(It.IsAny(), It.IsAny())) + .Callback(() => _socketMock.SetupGet(s => s.State).Returns(WebSocketState.Open)); + + _socketMock.Setup(_ => _.SendAsync(It.IsAny>(), WebSocketMessageType.Text, true, It.IsAny())) + .Callback, WebSocketMessageType, bool, CancellationToken>((mem, _, _, _) => sentPayloadCaptureCallback(mem)); + + _socketMock.Setup(_ => _.ReceiveAsync(It.IsAny>(), It.IsAny())). + Callback, CancellationToken>((mem, _) => + { + if (!_isSubConfirmed) + { + _subConfirmEvent.WaitOne(); + subConfirmContent.CopyTo(mem); + _isSubConfirmed = true; + } + else if (!_hasNotified) + { + notificationContents.CopyTo(mem); + _hasNotified = true; + + _socketMock.SetupGet(s => s.State).Returns(WebSocketState.Closed); + } + else if (!_hasEnded) + { + _hasEnded = true; + } + + }).Returns(() => _hasEnded ? _valueTaskEnd : _hasNotified ? _valueTaskNotification : _valueTaskConfirmation); + + } + + [TestInitialize] + public void SetupTest() + { + _socketMock = new Mock(); + _notificationEvent = new ManualResetEvent(false); + _subConfirmEvent = new ManualResetEvent(false); + _isSubConfirmed = false; + _hasNotified = false; + _hasEnded = false; + } + + [TestMethod] + public void SubscribeAccountInfoTest() + { + var expected = File.ReadAllText("Resources/AccountSubscribe.json"); + var subConfirmContent = File.ReadAllBytes("Resources/SubscribeConfirm.json"); + var notificationContents = File.ReadAllBytes("Resources/AccountSubscribeNotification.json"); + ResponseValue resultNotification = null; + var result = new ReadOnlyMemory(); + + SetupAction(out Action> action, + (x) => resultNotification = x, + (x) => result = x, + subConfirmContent, + notificationContents); + + var sut = new SolanaStreamingRpcClient("wss://api.mainnet-beta.solana.com/", null, _socketMock.Object); + + const string pubKey = "CM78CPUeXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNH12"; + + sut.Init().Wait(); + _ = sut.SubscribeAccountInfo(pubKey, action); + _subConfirmEvent.Set(); + + _socketMock.Verify(s => s.SendAsync(It.IsAny>(), + WebSocketMessageType.Text, + true, + It.IsAny())); + var res = Encoding.UTF8.GetString(result.Span); + Assert.AreEqual(expected, res); + + Assert.IsTrue(_notificationEvent.WaitOne()); + Assert.AreEqual(5199307UL, resultNotification.Context.Slot); + Assert.AreEqual("11111111111111111111111111111111", resultNotification.Value.Owner); + Assert.AreEqual(33594UL, resultNotification.Value.Lamports); + Assert.AreEqual(635UL, resultNotification.Value.RentEpoch); + Assert.AreEqual(false, resultNotification.Value.Executable); + } + + + [TestMethod] + public void UnsubscribeTest() + { + var subConfirmContent = File.ReadAllBytes("Resources/SubscribeConfirm.json"); + var unsubContents = File.ReadAllBytes("Resources/AccountSubUnsubscription.json"); + var unsubscribed = false; + + SetupAction(out Action> action, + _ => { }, + _ => { }, + subConfirmContent, + unsubContents); + + var sut = new SolanaStreamingRpcClient("wss://api.mainnet-beta.solana.com/", null, _socketMock.Object); + + const string pubKey = "CM78CPUeXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNH12"; + + sut.Init().Wait(); + var sub = sut.SubscribeAccountInfo(pubKey, action); + sub.SubscriptionChanged += (_, e) => + { + if (e.Status == SubscriptionStatus.Unsubscribed) + { + unsubscribed = true; + _notificationEvent.Set(); + } + }; + + _subConfirmEvent.Set(); + + sub.Unsubscribe(); + + Assert.IsTrue(_notificationEvent.WaitOne()); + Assert.IsTrue(unsubscribed); + } + + [TestMethod] + public void SubscribeLogsMentionTest() + { + var expected = File.ReadAllText("Resources/LogsSubscribeMention.json"); + var subConfirmContent = File.ReadAllBytes("Resources/SubscribeConfirm.json"); + ResponseValue resultNotification = null; + var result = new ReadOnlyMemory(); + + SetupAction(out Action> action, + (x) => resultNotification = x, + (x) => result = x, + subConfirmContent, + new byte[0]); + + var sut = new SolanaStreamingRpcClient("wss://api.mainnet-beta.solana.com/", null, _socketMock.Object); + + const string pubKey = "11111111111111111111111111111111"; + + sut.Init().Wait(); + _ = sut.SubscribeLogInfo(pubKey, action); + + _socketMock.Verify(s => s.SendAsync(It.IsAny>(), + WebSocketMessageType.Text, + true, + It.IsAny())); + var res = Encoding.UTF8.GetString(result.Span); + Assert.AreEqual(expected, res); + + } + + [TestMethod] + public void SubscribeLogsAllTest() + { + var expected = File.ReadAllText("Resources/LogsSubscribeAll.json"); + var subConfirmContent = File.ReadAllBytes("Resources/SubscribeConfirm.json"); + var notificationContents = File.ReadAllBytes("Resources/LogsSubscribeNotification.json"); + ResponseValue resultNotification = null; + var result = new ReadOnlyMemory(); + + SetupAction(out Action> action, + (x) => resultNotification = x, + (x) => result = x, + subConfirmContent, + notificationContents); + + var sut = new SolanaStreamingRpcClient("wss://api.mainnet-beta.solana.com/", null, _socketMock.Object); + + sut.Init().Wait(); + _ = sut.SubscribeLogInfo(Types.LogsSubscriptionType.All, action); + _subConfirmEvent.Set(); + + _socketMock.Verify(s => s.SendAsync(It.IsAny>(), + WebSocketMessageType.Text, + true, + It.IsAny())); + var res = Encoding.UTF8.GetString(result.Span); + Assert.AreEqual(expected, res); + + Assert.IsTrue(_notificationEvent.WaitOne()); + Assert.AreEqual(5208469UL, resultNotification.Context.Slot); + Assert.AreEqual("5h6xBEauJ3PK6SWCZ1PGjBvj8vDdWG3KpwATGy1ARAXFSDwt8GFXM7W5Ncn16wmqokgpiKRLuS83KUxyZyv2sUYv", resultNotification.Value.Signature); + Assert.IsNull(resultNotification.Value.Error); + Assert.AreEqual("BPF program 83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri success", resultNotification.Value.Logs[0]); + } + + + [TestMethod] + public void SubscribeProgramTest() + { + var expected = File.ReadAllText("Resources/ProgramSubscribe.json"); + var subConfirmContent = File.ReadAllBytes("Resources/SubscribeConfirm.json"); + var notificationContents = File.ReadAllBytes("Resources/ProgramSubscribeNotification.json"); + ResponseValue resultNotification = null; + var result = new ReadOnlyMemory(); + + SetupAction(out Action> action, + (x) => resultNotification = x, + (x) => result = x, + subConfirmContent, + notificationContents); + + var sut = new SolanaStreamingRpcClient("wss://api.mainnet-beta.solana.com/", null, _socketMock.Object); + + sut.Init().Wait(); + _ = sut.SubscribeProgram("11111111111111111111111111111111", action); + _subConfirmEvent.Set(); + + _socketMock.Verify(s => s.SendAsync(It.IsAny>(), + WebSocketMessageType.Text, + true, + It.IsAny())); + var res = Encoding.UTF8.GetString(result.Span); + Assert.AreEqual(expected, res); + + Assert.IsTrue(_notificationEvent.WaitOne()); + Assert.AreEqual(80854485UL, resultNotification.Context.Slot); + Assert.AreEqual("9FXD1NXrK6xFU8i4gLAgjj2iMEWTqJhSuQN8tQuDfm2e", resultNotification.Value.PublicKey); + Assert.AreEqual("11111111111111111111111111111111", resultNotification.Value.Account.Owner); + Assert.AreEqual(false, resultNotification.Value.Account.Executable); + Assert.AreEqual(187UL, resultNotification.Value.Account.RentEpoch); + Assert.AreEqual(458553192193UL, resultNotification.Value.Account.Lamports); + } + + [TestMethod] + public void SubscribeSlotTest() + { + var expected = File.ReadAllText("Resources/SlotSubscribe.json"); + var subConfirmContent = File.ReadAllBytes("Resources/SubscribeConfirm.json"); + var notificationContents = File.ReadAllBytes("Resources/SlotSubscribeNotification.json"); + SlotInfo resultNotification = null; + var result = new ReadOnlyMemory(); + + SetupAction(out Action action, + (x) => resultNotification = x, + (x) => result = x, + subConfirmContent, + notificationContents); + + var sut = new SolanaStreamingRpcClient("wss://api.mainnet-beta.solana.com/", null, _socketMock.Object); + + sut.Init().Wait(); + _ = sut.SubscribeSlotInfo(action); + _subConfirmEvent.Set(); + + _socketMock.Verify(s => s.SendAsync(It.IsAny>(), + WebSocketMessageType.Text, + true, + It.IsAny())); + var res = Encoding.UTF8.GetString(result.Span); + Assert.AreEqual(expected, res); + + Assert.IsTrue(_notificationEvent.WaitOne()); + Assert.AreEqual(75, resultNotification.Parent); + Assert.AreEqual(44, resultNotification.Root); + Assert.AreEqual(76, resultNotification.Slot); + } + + + [TestMethod] + public void SubscribeRootTest() + { + var expected = File.ReadAllText("Resources/RootSubscribe.json"); + var subConfirmContent = File.ReadAllBytes("Resources/SubscribeConfirm.json"); + var notificationContents = File.ReadAllBytes("Resources/RootSubscribeNotification.json"); + int resultNotification = 0; + var result = new ReadOnlyMemory(); + + SetupAction(out Action action, + (x) => resultNotification = x, + (x) => result = x, + subConfirmContent, + notificationContents); + + var sut = new SolanaStreamingRpcClient("wss://api.mainnet-beta.solana.com/", null, _socketMock.Object); + + sut.Init().Wait(); + var sub = sut.SubscribeRoot(action); + _subConfirmEvent.Set(); + + _socketMock.Verify(s => s.SendAsync(It.IsAny>(), + WebSocketMessageType.Text, + true, + It.IsAny())); + var res = Encoding.UTF8.GetString(result.Span); + Assert.AreEqual(expected, res); + + Assert.IsTrue(_notificationEvent.WaitOne()); + Assert.AreEqual(42, resultNotification); + } + + + + [TestMethod] + public void SubscribeSignatureTest() + { + var expected = File.ReadAllText("Resources/SignatureSubscribe.json"); + var subConfirmContent = File.ReadAllBytes("Resources/SubscribeConfirm.json"); + var notificationContents = File.ReadAllBytes("Resources/SignatureSubscribeNotification.json"); + ResponseValue resultNotification = null; + AutoResetEvent subscriptionEvent = new AutoResetEvent(false); + var result = new ReadOnlyMemory(); + + SetupAction(out Action> action, + (x) => resultNotification = x, + (x) => result = x, + subConfirmContent, + notificationContents); + + var sut = new SolanaStreamingRpcClient("wss://api.mainnet-beta.solana.com/", null, _socketMock.Object); + + SubscriptionEvent evt = null; + + + sut.Init().Wait(); + var sub = sut.SubscribeSignature("4orRpuqStpJDvcpBy3vDSV4TDTGNbefmqYUnG2yVnKwjnLFqCwY4h5cBTAKakKek4inuxHF71LuscBS1vwSLtWcx", action); + sub.SubscriptionChanged += (s, e) => + { + evt = e; + if (e.Status == SubscriptionStatus.Unsubscribed) + subscriptionEvent.Set(); + }; + _subConfirmEvent.Set(); + + _socketMock.Verify(s => s.SendAsync(It.IsAny>(), + WebSocketMessageType.Text, + true, + It.IsAny())); + var res = Encoding.UTF8.GetString(result.Span); + Assert.AreEqual(expected, res); + + Assert.IsTrue(_notificationEvent.WaitOne()); + Assert.AreEqual("dummy error", resultNotification.Value.Error); + + Assert.IsTrue(subscriptionEvent.WaitOne()); + Assert.AreEqual(SubscriptionStatus.Unsubscribed, evt.Status); + Assert.AreEqual(SubscriptionStatus.Unsubscribed, sub.State); + } + + + [TestMethod] + public void SubscribeBadAccountTest() + { + var expected = File.ReadAllText("Resources/BadAccountSubscribe.json"); + var subConfirmContent = File.ReadAllBytes("Resources/BadAccountSubscribeResult.json"); + var result = new ReadOnlyMemory(); + AutoResetEvent subscriptionEvent = new AutoResetEvent(false); + SetupAction(out Action> action, + _ => { }, + (x) => result = x, + subConfirmContent, + new byte[0]); + + var sut = new SolanaStreamingRpcClient("wss://api.mainnet-beta.solana.com/", null, _socketMock.Object); + + const string pubKey = "invalidkey1"; + + sut.Init().Wait(); + var sub = sut.SubscribeAccountInfo(pubKey, action); + SubscriptionEvent subEvent = null; + sub.SubscriptionChanged += (sub, evt) => + { + subEvent = evt; + subscriptionEvent.Set(); + }; + + _subConfirmEvent.Set(); + + _socketMock.Verify(s => s.SendAsync(It.IsAny>(), + WebSocketMessageType.Text, + true, + It.IsAny())); + var res = Encoding.UTF8.GetString(result.Span); + Assert.AreEqual(expected, res); + + Assert.IsTrue(subscriptionEvent.WaitOne()); + Assert.AreEqual("-32602", subEvent.Code); + Assert.AreEqual(SubscriptionStatus.ErrorSubscribing, subEvent.Status); + Assert.AreEqual("Invalid Request: Invalid pubkey provided", subEvent.Error); + + Assert.AreEqual(SubscriptionStatus.ErrorSubscribing, sub.State); + } + + + + [TestMethod] + public void SubscribeAccountBigPayloadTest() + { + var expected = File.ReadAllText("Resources/BigAccountSubscribe.json"); + var subConfirmContent = File.ReadAllBytes("Resources/SubscribeConfirm.json"); + var notifContent = File.ReadAllBytes("Resources/BigAccountNotificationPayload.json"); + var expectedDataContent = File.ReadAllText("Resources/BigAccountNotificationPayloadData.txt"); + var result = new ReadOnlyMemory(); + + AutoResetEvent signal = new AutoResetEvent(false); + int currentMessageIdx = 0; + //confirm + bigpayload divided in two read steps + empty payload + var payloads = new Memory[] + { + new Memory(subConfirmContent), + new Memory(notifContent, 0, 32768), + new Memory(notifContent, 32768, notifContent.Length - 32768), + Memory.Empty + + }; + + AutoResetEvent subscriptionEvent = new AutoResetEvent(false); + ResponseValue notificationValue = null; + + var actionMock = new Mock>>(); + actionMock.Setup(_ => _(It.IsAny(), It.IsAny>())).Callback>((_, notifValue) => + { + notificationValue = notifValue; + _notificationEvent.Set(); + }); + + _socketMock.Setup(_ => _.ConnectAsync(It.IsAny(), It.IsAny())) + .Callback(() => _socketMock.SetupGet(s => s.State).Returns(WebSocketState.Open)); + + _socketMock.Setup(_ => _.SendAsync(It.IsAny>(), WebSocketMessageType.Text, true, It.IsAny())) + .Callback, WebSocketMessageType, bool, CancellationToken>((mem, _, _, _) => result = mem); + + _ = _socketMock.Setup(_ => _.ReceiveAsync(It.IsAny>(), It.IsAny())). + Callback, CancellationToken>((mem, _) => + { + if (currentMessageIdx == 0) + signal.WaitOne(); + payloads[currentMessageIdx++].CopyTo(mem); + }).Returns(() => new ValueTask( + new ValueWebSocketReceiveResult( + payloads[currentMessageIdx - 1].Length, + payloads[currentMessageIdx - 1].Length == 0 ? WebSocketMessageType.Close : WebSocketMessageType.Text, + currentMessageIdx == 2 ? false : true))); + + var sut = new SolanaStreamingRpcClient("wss://api.mainnet-beta.solana.com/", null, _socketMock.Object); + + const string pubKey = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"; + + sut.Init().Wait(); + var sub = sut.SubscribeAccountInfo(pubKey, actionMock.Object); + SubscriptionEvent subEvent = null; + sub.SubscriptionChanged += (sub, evt) => + { + subEvent = evt; + subscriptionEvent.Set(); + }; + + signal.Set(); + + _socketMock.Verify(s => s.SendAsync(It.IsAny>(), + WebSocketMessageType.Text, + true, + It.IsAny())); + + var res = Encoding.UTF8.GetString(result.Span); + Assert.AreEqual(expected, res); + + Assert.IsTrue(subscriptionEvent.WaitOne()); + Assert.IsTrue(string.IsNullOrEmpty(subEvent.Code)); + Assert.AreEqual(SubscriptionStatus.Subscribed, subEvent.Status); + Assert.IsTrue(string.IsNullOrEmpty(subEvent.Error)); + Assert.AreEqual(SubscriptionStatus.Subscribed, sub.State); + + Assert.IsTrue(_notificationEvent.WaitOne()); + + Assert.AreEqual(expectedDataContent, notificationValue.Value.Data[0]); + Assert.AreEqual("base64", notificationValue.Value.Data[1]); + } + } } \ No newline at end of file diff --git a/test/Solnet.Rpc.Test/TransactionBuilderTest.cs b/test/Solnet.Rpc.Test/TransactionBuilderTest.cs index e69bcb57..1cb87778 100644 --- a/test/Solnet.Rpc.Test/TransactionBuilderTest.cs +++ b/test/Solnet.Rpc.Test/TransactionBuilderTest.cs @@ -1,116 +1,116 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Solnet.Programs; -using Solnet.Rpc.Builders; -using Solnet.Wallet; -using System; -using System.Collections.Generic; - -namespace Solnet.Rpc.Test -{ - [TestClass] - public class TransactionBuilderTest - { - private const string MnemonicWords = - "route clerk disease box emerge airport loud waste attitude film army tray" + - " forward deal onion eight catalog surface unit card window walnut wealth medal"; - - private const string Blockhash = "5cZja93sopRB9Bkhckj5WzCxCaVyriv2Uh5fFDPDFFfj"; - - private const string ExpectedTransactionHashWithTransferAndMemo = - "AV9Xyi1t5dscb5+097PVDAP8fq/6HDRoNTQx9ZD2picvZNDUy9seCEKgsTNKgeTXtQ+pNEYB" + - "4PfxPX+9bQI7hgkBAAIEUy4zulRg8z2yKITZaNwcnq6G6aH8D0ITae862qbJ+3eE3M6r5DRw" + - "ldquwlqOuXDDOWZagXmbHnAU3w5Dg44kogAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + - "AAAABUpTUPhdyILWFKVWcniKKW3fHqur0KYGeIhJMvTu9qBEixald4nI54jqHpYLSWViej50" + - "bnmzhen0yUOsH2zbbgICAgABDAIAAACAlpgAAAAAAAMBABVIZWxsbyBmcm9tIFNvbC5OZXQgOik="; - - private const string ExpectedTransactionHashCreateInitializeAndMintTo = - "A5X22for3AxcX09IKX5Cbrpvv4k/1TcdTY2wf6vkq7Wcb/3fwMjA0vCshKkBG0EXQM2oKanIaQilKC/L" + - "KLmTYwc2yOVXu0TZCGwraCrxf4Pr8KpvTZZcUz/s4sls3VzGRqQmIhR3nXBR/O3\u002B4ZdICd8hYXb" + - "USqUBE\u002B4qCwpbC7gLlVo1ErARFL9csoTPvxA3/00wTxbs01sXlAH5t\u002ByAiwlan7B24Za3d" + - "CYydaczAOenGVU0nxBrz/gdFZgCJArZAAMABAdHaauXIEuoP7DK7hf3ho8eB05SFYGg2J2UN52qZbcXs" + - "k\u002BnIqdN4P6YFyTS64cak6Wd2hx9Qsbwf4gfPc5VPJvFTT4lvYz77q8imSqvzO/5qiFW9tKqfO4l5F" + - "KhFh6lZQsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAan1RcZLFxRIYzJTD1K8X9Y2u4Im6H9" + - "ROPb2YoAAAAABt324ddloZPZy\u002BFGzut5rBy0he1fWzeROoz1hX7/AKkFSlNQ\u002BF3IgtYUpVZye" + - "Iopbd8eq6vQpgZ4iEky9O72oOD/Y3arpTMrvjv2uP0ZD3LVkDTmRAfOpQ603IYXOGjCBgMCAAE0AAAAAGBN" + - "FgAAAAAAUgAAAAAAAAAG3fbh12Whk9nL4UbO63msHLSF7V9bN5E6jPWFfv8AqQUCAQRDAAJHaauXIEuoP7DK" + - "7hf3ho8eB05SFYGg2J2UN52qZbcXsgFHaauXIEuoP7DK7hf3ho8eB05SFYGg2J2UN52qZbcXsgMCAAI0AAAA" + - "APAdHwAAAAAApQAAAAAAAAAG3fbh12Whk9nL4UbO63msHLSF7V9bN5E6jPWFfv8AqQUEAgEABAEBBQMBAgA" + - "JB6hhAAAAAAAABgECEkhlbGxvIGZyb20gU29sLk5ldA=="; - - [TestMethod] - public void TestTransactionBuilderBuild() - { - var wallet = new Wallet.Wallet(MnemonicWords); - var fromAccount = wallet.GetAccount(0); - var toAccount = wallet.GetAccount(1); - var tx = new TransactionBuilder().SetRecentBlockHash(Blockhash) - .AddInstruction(SystemProgram.Transfer(fromAccount.GetPublicKey, toAccount.GetPublicKey, 10000000)) - .AddInstruction(MemoProgram.NewMemo(fromAccount, "Hello from Sol.Net :)")).Build(fromAccount); - - Assert.AreEqual(ExpectedTransactionHashWithTransferAndMemo, Convert.ToBase64String(tx)); - } - - [TestMethod] - [ExpectedException(typeof(Exception))] - public void TestTransactionBuilderBuildNullBlockhashException() - { - var wallet = new Wallet.Wallet(MnemonicWords); - var fromAccount = wallet.GetAccount(0); - var toAccount = wallet.GetAccount(1); - _ = new TransactionBuilder() - .AddInstruction(SystemProgram.Transfer(fromAccount.GetPublicKey, toAccount.GetPublicKey, 10000000)) - .AddInstruction(MemoProgram.NewMemo(fromAccount, "Hello from Sol.Net :)")).Build(fromAccount); - } - - [TestMethod] - [ExpectedException(typeof(Exception))] - public void TestTransactionBuilderBuildNullInstructionsException() - { - var wallet = new Wallet.Wallet(MnemonicWords); - var fromAccount = wallet.GetAccount(0); - _ = new TransactionBuilder().SetRecentBlockHash(Blockhash).Build(fromAccount); - } - - [TestMethod] - public void CreateInitializeAndMintToTest() - { - var wallet = new Wallet.Wallet(MnemonicWords); - - var blockHash = "G9JC6E7LfG6ayxARq5zDV5RdDr6P8NJEdzTUJ8ttrSKs"; - var minBalanceForAccount = 2039280L; - var minBalanceForMintAccount = 1461600L; - - var mintAccount = wallet.GetAccount(17); - var ownerAccount = wallet.GetAccount(10); - var initialAccount = wallet.GetAccount(18); - - - var tx = new TransactionBuilder().SetRecentBlockHash(blockHash).AddInstruction(SystemProgram.CreateAccount( - ownerAccount.GetPublicKey, - mintAccount.GetPublicKey, - minBalanceForMintAccount, - TokenProgram.MintAccountDataSize, - TokenProgram.ProgramId)).AddInstruction(TokenProgram.InitializeMint( - mintAccount.GetPublicKey, - 2, - ownerAccount.GetPublicKey, - ownerAccount.GetPublicKey)).AddInstruction(SystemProgram.CreateAccount( - ownerAccount.GetPublicKey, - initialAccount.GetPublicKey, - minBalanceForAccount, - SystemProgram.AccountDataSize, - TokenProgram.ProgramId)).AddInstruction(TokenProgram.InitializeAccount( - initialAccount.GetPublicKey, - mintAccount.GetPublicKey, - ownerAccount.GetPublicKey)).AddInstruction(TokenProgram.MintTo( - mintAccount.GetPublicKey, - initialAccount.GetPublicKey, - 25000, - ownerAccount.GetPublicKey)) - .AddInstruction(MemoProgram.NewMemo(initialAccount, "Hello from Sol.Net")) - .Build(new List { ownerAccount, mintAccount, initialAccount }); - - Assert.AreEqual(ExpectedTransactionHashCreateInitializeAndMintTo, Convert.ToBase64String(tx)); - } - } +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Solnet.Programs; +using Solnet.Rpc.Builders; +using Solnet.Wallet; +using System; +using System.Collections.Generic; + +namespace Solnet.Rpc.Test +{ + [TestClass] + public class TransactionBuilderTest + { + private const string MnemonicWords = + "route clerk disease box emerge airport loud waste attitude film army tray" + + " forward deal onion eight catalog surface unit card window walnut wealth medal"; + + private const string Blockhash = "5cZja93sopRB9Bkhckj5WzCxCaVyriv2Uh5fFDPDFFfj"; + + private const string ExpectedTransactionHashWithTransferAndMemo = + "AV9Xyi1t5dscb5+097PVDAP8fq/6HDRoNTQx9ZD2picvZNDUy9seCEKgsTNKgeTXtQ+pNEYB" + + "4PfxPX+9bQI7hgkBAAIEUy4zulRg8z2yKITZaNwcnq6G6aH8D0ITae862qbJ+3eE3M6r5DRw" + + "ldquwlqOuXDDOWZagXmbHnAU3w5Dg44kogAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + + "AAAABUpTUPhdyILWFKVWcniKKW3fHqur0KYGeIhJMvTu9qBEixald4nI54jqHpYLSWViej50" + + "bnmzhen0yUOsH2zbbgICAgABDAIAAACAlpgAAAAAAAMBABVIZWxsbyBmcm9tIFNvbC5OZXQgOik="; + + private const string ExpectedTransactionHashCreateInitializeAndMintTo = + "A5X22for3AxcX09IKX5Cbrpvv4k/1TcdTY2wf6vkq7Wcb/3fwMjA0vCshKkBG0EXQM2oKanIaQilKC/L" + + "KLmTYwc2yOVXu0TZCGwraCrxf4Pr8KpvTZZcUz/s4sls3VzGRqQmIhR3nXBR/O3\u002B4ZdICd8hYXb" + + "USqUBE\u002B4qCwpbC7gLlVo1ErARFL9csoTPvxA3/00wTxbs01sXlAH5t\u002ByAiwlan7B24Za3d" + + "CYydaczAOenGVU0nxBrz/gdFZgCJArZAAMABAdHaauXIEuoP7DK7hf3ho8eB05SFYGg2J2UN52qZbcXs" + + "k\u002BnIqdN4P6YFyTS64cak6Wd2hx9Qsbwf4gfPc5VPJvFTT4lvYz77q8imSqvzO/5qiFW9tKqfO4l5F" + + "KhFh6lZQsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAan1RcZLFxRIYzJTD1K8X9Y2u4Im6H9" + + "ROPb2YoAAAAABt324ddloZPZy\u002BFGzut5rBy0he1fWzeROoz1hX7/AKkFSlNQ\u002BF3IgtYUpVZye" + + "Iopbd8eq6vQpgZ4iEky9O72oOD/Y3arpTMrvjv2uP0ZD3LVkDTmRAfOpQ603IYXOGjCBgMCAAE0AAAAAGBN" + + "FgAAAAAAUgAAAAAAAAAG3fbh12Whk9nL4UbO63msHLSF7V9bN5E6jPWFfv8AqQUCAQRDAAJHaauXIEuoP7DK" + + "7hf3ho8eB05SFYGg2J2UN52qZbcXsgFHaauXIEuoP7DK7hf3ho8eB05SFYGg2J2UN52qZbcXsgMCAAI0AAAA" + + "APAdHwAAAAAApQAAAAAAAAAG3fbh12Whk9nL4UbO63msHLSF7V9bN5E6jPWFfv8AqQUEAgEABAEBBQMBAgA" + + "JB6hhAAAAAAAABgECEkhlbGxvIGZyb20gU29sLk5ldA=="; + + [TestMethod] + public void TestTransactionBuilderBuild() + { + var wallet = new Wallet.Wallet(MnemonicWords); + var fromAccount = wallet.GetAccount(0); + var toAccount = wallet.GetAccount(1); + var tx = new TransactionBuilder().SetRecentBlockHash(Blockhash) + .AddInstruction(SystemProgram.Transfer(fromAccount.GetPublicKey, toAccount.GetPublicKey, 10000000)) + .AddInstruction(MemoProgram.NewMemo(fromAccount, "Hello from Sol.Net :)")).Build(fromAccount); + + Assert.AreEqual(ExpectedTransactionHashWithTransferAndMemo, Convert.ToBase64String(tx)); + } + + [TestMethod] + [ExpectedException(typeof(Exception))] + public void TestTransactionBuilderBuildNullBlockhashException() + { + var wallet = new Wallet.Wallet(MnemonicWords); + var fromAccount = wallet.GetAccount(0); + var toAccount = wallet.GetAccount(1); + _ = new TransactionBuilder() + .AddInstruction(SystemProgram.Transfer(fromAccount.GetPublicKey, toAccount.GetPublicKey, 10000000)) + .AddInstruction(MemoProgram.NewMemo(fromAccount, "Hello from Sol.Net :)")).Build(fromAccount); + } + + [TestMethod] + [ExpectedException(typeof(Exception))] + public void TestTransactionBuilderBuildNullInstructionsException() + { + var wallet = new Wallet.Wallet(MnemonicWords); + var fromAccount = wallet.GetAccount(0); + _ = new TransactionBuilder().SetRecentBlockHash(Blockhash).Build(fromAccount); + } + + [TestMethod] + public void CreateInitializeAndMintToTest() + { + var wallet = new Wallet.Wallet(MnemonicWords); + + var blockHash = "G9JC6E7LfG6ayxARq5zDV5RdDr6P8NJEdzTUJ8ttrSKs"; + var minBalanceForAccount = 2039280L; + var minBalanceForMintAccount = 1461600L; + + var mintAccount = wallet.GetAccount(17); + var ownerAccount = wallet.GetAccount(10); + var initialAccount = wallet.GetAccount(18); + + + var tx = new TransactionBuilder().SetRecentBlockHash(blockHash).AddInstruction(SystemProgram.CreateAccount( + ownerAccount.GetPublicKey, + mintAccount.GetPublicKey, + minBalanceForMintAccount, + TokenProgram.MintAccountDataSize, + TokenProgram.ProgramId)).AddInstruction(TokenProgram.InitializeMint( + mintAccount.GetPublicKey, + 2, + ownerAccount.GetPublicKey, + ownerAccount.GetPublicKey)).AddInstruction(SystemProgram.CreateAccount( + ownerAccount.GetPublicKey, + initialAccount.GetPublicKey, + minBalanceForAccount, + SystemProgram.AccountDataSize, + TokenProgram.ProgramId)).AddInstruction(TokenProgram.InitializeAccount( + initialAccount.GetPublicKey, + mintAccount.GetPublicKey, + ownerAccount.GetPublicKey)).AddInstruction(TokenProgram.MintTo( + mintAccount.GetPublicKey, + initialAccount.GetPublicKey, + 25000, + ownerAccount.GetPublicKey)) + .AddInstruction(MemoProgram.NewMemo(initialAccount, "Hello from Sol.Net")) + .Build(new List { ownerAccount, mintAccount, initialAccount }); + + Assert.AreEqual(ExpectedTransactionHashCreateInitializeAndMintTo, Convert.ToBase64String(tx)); + } + } } \ No newline at end of file diff --git a/test/Solnet.Rpc.Test/UtilitiesTest.cs b/test/Solnet.Rpc.Test/UtilitiesTest.cs index 38246d9c..de9dc9b2 100644 --- a/test/Solnet.Rpc.Test/UtilitiesTest.cs +++ b/test/Solnet.Rpc.Test/UtilitiesTest.cs @@ -1,64 +1,64 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using NBitcoin.DataEncoders; -using Solnet.Rpc.Utilities; -using System; -using System.Text; - -namespace Solnet.Rpc.Test -{ - [TestClass] - public class UtilitiesTest - { - private const string LoaderProgramId = "BPFLoader1111111111111111111111111111111111"; - - - [TestMethod] - [ExpectedException(typeof(ArgumentException))] - public void TestCreateProgramAddressException() - { - _ = AddressExtensions.CreateProgramAddress( - new[] { Encoding.UTF8.GetBytes("SeedPubey1111111111111111111111111111111111") }, - Encoding.UTF8.GetBytes(LoaderProgramId)); - } - - [TestMethod] - public void TestCreateProgramAddress() - { - var b58 = new Base58Encoder(); - var programAddress = AddressExtensions.CreateProgramAddress( - new[] { b58.DecodeData("SeedPubey1111111111111111111111111111111111") }, - b58.DecodeData(LoaderProgramId)); - - CollectionAssert.AreEqual( - b58.DecodeData("GUs5qLUfsEHkcMB9T38vjr18ypEhRuNWiePW2LoK4E3K"), programAddress); - - programAddress = AddressExtensions.CreateProgramAddress( - new[] { Encoding.UTF8.GetBytes(""), new byte[] { 1 } }, - b58.DecodeData(LoaderProgramId)); - - CollectionAssert.AreEqual( - b58.DecodeData("3gF2KMe9KiC6FNVBmfg9i267aMPvK37FewCip4eGBFcT"), programAddress); - - programAddress = AddressExtensions.CreateProgramAddress( - new[] { Encoding.UTF8.GetBytes("☉") }, - b58.DecodeData(LoaderProgramId)); - - CollectionAssert.AreEqual( - b58.DecodeData("7ytmC1nT1xY4RfxCV2ZgyA7UakC93do5ZdyhdF3EtPj7"), programAddress); - } - - [TestMethod] - public void TestFindProgramAddress() - { - var programAddress = AddressExtensions.FindProgramAddress( - new[] { Encoding.UTF8.GetBytes("") }, - Encoding.UTF8.GetBytes(LoaderProgramId)); - - CollectionAssert.AreEqual( - programAddress.Address, - AddressExtensions.CreateProgramAddress( - new[] { Encoding.UTF8.GetBytes(""), new[] { (byte)programAddress.Nonce } }, - Encoding.UTF8.GetBytes(LoaderProgramId))); - } - } +using Microsoft.VisualStudio.TestTools.UnitTesting; +using NBitcoin.DataEncoders; +using Solnet.Rpc.Utilities; +using System; +using System.Text; + +namespace Solnet.Rpc.Test +{ + [TestClass] + public class UtilitiesTest + { + private const string LoaderProgramId = "BPFLoader1111111111111111111111111111111111"; + + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void TestCreateProgramAddressException() + { + _ = AddressExtensions.CreateProgramAddress( + new[] { Encoding.UTF8.GetBytes("SeedPubey1111111111111111111111111111111111") }, + Encoding.UTF8.GetBytes(LoaderProgramId)); + } + + [TestMethod] + public void TestCreateProgramAddress() + { + var b58 = new Base58Encoder(); + var programAddress = AddressExtensions.CreateProgramAddress( + new[] { b58.DecodeData("SeedPubey1111111111111111111111111111111111") }, + b58.DecodeData(LoaderProgramId)); + + CollectionAssert.AreEqual( + b58.DecodeData("GUs5qLUfsEHkcMB9T38vjr18ypEhRuNWiePW2LoK4E3K"), programAddress); + + programAddress = AddressExtensions.CreateProgramAddress( + new[] { Encoding.UTF8.GetBytes(""), new byte[] { 1 } }, + b58.DecodeData(LoaderProgramId)); + + CollectionAssert.AreEqual( + b58.DecodeData("3gF2KMe9KiC6FNVBmfg9i267aMPvK37FewCip4eGBFcT"), programAddress); + + programAddress = AddressExtensions.CreateProgramAddress( + new[] { Encoding.UTF8.GetBytes("☉") }, + b58.DecodeData(LoaderProgramId)); + + CollectionAssert.AreEqual( + b58.DecodeData("7ytmC1nT1xY4RfxCV2ZgyA7UakC93do5ZdyhdF3EtPj7"), programAddress); + } + + [TestMethod] + public void TestFindProgramAddress() + { + var programAddress = AddressExtensions.FindProgramAddress( + new[] { Encoding.UTF8.GetBytes("") }, + Encoding.UTF8.GetBytes(LoaderProgramId)); + + CollectionAssert.AreEqual( + programAddress.Address, + AddressExtensions.CreateProgramAddress( + new[] { Encoding.UTF8.GetBytes(""), new[] { (byte)programAddress.Nonce } }, + Encoding.UTF8.GetBytes(LoaderProgramId))); + } + } } \ No newline at end of file diff --git a/test/Solnet.Wallet.Test/AccountTest.cs b/test/Solnet.Wallet.Test/AccountTest.cs index 07621289..02eacd0f 100644 --- a/test/Solnet.Wallet.Test/AccountTest.cs +++ b/test/Solnet.Wallet.Test/AccountTest.cs @@ -1,136 +1,136 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using System; - -namespace Solnet.Wallet.Test -{ - [TestClass] - public class AccountTest - { - private static readonly byte[] PrivateKey = - { - 227, 215, 255, 79, 160, 83, 24, 167, 124, 73, 168, 45, - 235, 105, 253, 165, 194, 54, 12, 95, 5, 47, 21, 158, 120, - 155, 199, 182, 101, 212, 80, 173, 138, 180, 156, 252, 109, - 252, 108, 26, 186, 0, 196, 69, 57, 102, 15, 151, 149, 242, - 119, 181, 171, 113, 120, 224, 0, 118, 155, 61, 246, 56, 178, 47 - }; - private static readonly byte[] PublicKey = - { - 138, 180, 156, 252, 109, 252, 108, 26, 186, 0, - 196, 69, 57, 102, 15, 151, 149, 242, 119, 181, - 171, 113, 120, 224, 0, 118, 155, 61, 246, 56, 178, 47 - }; - - private static readonly byte[] InvalidPrivateKey = - { - 227, 215, 255, 79, 160, 83, 24, 167, 124, 73, 168, 45, - 235, 105, 253, 165, 194, 54, 12, 95, 5, 47, 21, 158, 120, - 155, 199, 182, 101, 212, 80, 173, 138, 180, 156, 252, 109, - 252, 108, 26, 186, 0, 196, 69, 57, 242, 119, 181, 171, 113, - 120, 224, 0, 118, 155, 61, 246, 56, 178, 47 - }; - private static readonly byte[] InvalidPublicKey = - { - 138, 180, 156, 252, 109, 252, 108, 26, 186, 0, - 196, 69, 57, 102, 15, 151, 149, 242, 119, 181, - 171, 113, 120, 224, 0, 118, 155, 61 - }; - - - private static readonly byte[] SerializedMessage = - { - 1, 0, 2, 4, 138, 180, 156, 252, 109, 252, 108, 26, 186, 0, - 196, 69, 57, 102, 15, 151, 149, 242, 119, 181, 171, 113, - 120, 224, 0, 118, 155, 61, 246, 56, 178, 47, 173, 126, 102, - 53, 246, 163, 32, 189, 27, 84, 69, 94, 217, 196, 152, 178, - 198, 116, 124, 160, 230, 94, 226, 141, 220, 221, 119, 21, - 204, 242, 204, 164, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 74, 83, 80, - 248, 93, 200, 130, 214, 20, 165, 86, 114, 120, 138, 41, 109, - 223, 30, 171, 171, 208, 166, 6, 120, 136, 73, 50, 244, 238, - 246, 160, 61, 96, 239, 228, 59, 10, 206, 186, 110, 68, 55, - 160, 108, 50, 58, 247, 220, 116, 182, 121, 237, 126, 42, 184, - 248, 125, 83, 253, 85, 181, 215, 93, 2, 2, 2, 0, 1, 12, 2, 0, 0, 0, - 128, 150, 152, 0, 0, 0, 0, 0, 3, 1, 0, 21, 72, 101, 108, 108, 111, - 32, 102, 114, 111, 109, 32, 83, 111, 108, 46, 78, 101, 116, 32, 58, 41 - }; - private static readonly byte[] SerializedMessageSignature = - { - 234, 147, 144, 17, 200, 57, 8, 154, 139, 86, 156, 12, 7, 143, 144, - 85, 27, 151, 186, 223, 246, 231, 186, 81, 69, 107, 126, 76, 119, - 14, 112, 57, 38, 5, 28, 109, 99, 30, 249, 154, 87, 241, 28, 161, - 178, 165, 146, 73, 179, 4, 71, 133, 203, 145, 125, 252, 200, 249, - 38, 105, 30, 113, 73, 8 - }; - - private const string ExpectedEncodedPublicKey = "ALSzrjtGi8MZGmAZa6ZhtUZq3rwurWuJqWFdgcj9MMFL"; - private const string ExpectedEncodedPrivateKey = "5ZD7ntKtyHrnqMhfSuKBLdqHzT5N3a2aYnCGBcz4N78b84TKpjwQ4QBsapEnpnZFchM7F1BpqDkSuLdwMZwM8hLi"; - - [TestMethod] - public void TestAccountNoKeys() - { - var account = new Account(); - Assert.IsNotNull(account.PrivateKey, "account.PrivateKey != null"); - Assert.IsNotNull(account.PublicKey, "account.PublicKey != null"); - Assert.IsNotNull(account.GetPrivateKey, "account.GetPrivateKey != null"); - Assert.IsNotNull(account.GetPublicKey, "account.GetPublicKey != null"); - } - - [TestMethod] - [ExpectedException(typeof(ArgumentException))] - public void TestAccountInvalidKeys() - { - _ = new Account(InvalidPrivateKey, InvalidPublicKey); - } - - [TestMethod] - [ExpectedException(typeof(ArgumentException))] - public void TestAccountInvalidPrivateKey() - { - _ = new Account(InvalidPrivateKey, PublicKey); - } - - [TestMethod] - [ExpectedException(typeof(ArgumentException))] - public void TestAccountInvalidPublicKey() - { - _ = new Account(PrivateKey, InvalidPublicKey); - } - - [TestMethod] - public void TestAccountGetPrivateKey() - { - var account = new Account(PrivateKey, PublicKey); - CollectionAssert.AreEqual(PrivateKey, account.PrivateKey); - CollectionAssert.AreEqual(PublicKey, account.PublicKey); - Assert.AreEqual(ExpectedEncodedPublicKey, account.GetPublicKey); - } - - [TestMethod] - public void TestAccountGetPublicKey() - { - var account = new Account(PrivateKey, PublicKey); - CollectionAssert.AreEqual(PrivateKey, account.PrivateKey); - CollectionAssert.AreEqual(PublicKey, account.PublicKey); - Assert.AreEqual(ExpectedEncodedPrivateKey, account.GetPrivateKey); - } - - [TestMethod] - public void TestAccountSign() - { - var account = new Account(PrivateKey, PublicKey); - CollectionAssert.AreEqual(PrivateKey, account.PrivateKey); - CollectionAssert.AreEqual(PublicKey, account.PublicKey); - CollectionAssert.AreEqual(SerializedMessageSignature, account.Sign(SerializedMessage)); - } - - [TestMethod] - public void TestAccountVerify() - { - var account = new Account(PrivateKey, PublicKey); - CollectionAssert.AreEqual(PrivateKey, account.PrivateKey); - CollectionAssert.AreEqual(PublicKey, account.PublicKey); - Assert.IsTrue(account.Verify(SerializedMessage, SerializedMessageSignature)); - } - } +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; + +namespace Solnet.Wallet.Test +{ + [TestClass] + public class AccountTest + { + private static readonly byte[] PrivateKey = + { + 227, 215, 255, 79, 160, 83, 24, 167, 124, 73, 168, 45, + 235, 105, 253, 165, 194, 54, 12, 95, 5, 47, 21, 158, 120, + 155, 199, 182, 101, 212, 80, 173, 138, 180, 156, 252, 109, + 252, 108, 26, 186, 0, 196, 69, 57, 102, 15, 151, 149, 242, + 119, 181, 171, 113, 120, 224, 0, 118, 155, 61, 246, 56, 178, 47 + }; + private static readonly byte[] PublicKey = + { + 138, 180, 156, 252, 109, 252, 108, 26, 186, 0, + 196, 69, 57, 102, 15, 151, 149, 242, 119, 181, + 171, 113, 120, 224, 0, 118, 155, 61, 246, 56, 178, 47 + }; + + private static readonly byte[] InvalidPrivateKey = + { + 227, 215, 255, 79, 160, 83, 24, 167, 124, 73, 168, 45, + 235, 105, 253, 165, 194, 54, 12, 95, 5, 47, 21, 158, 120, + 155, 199, 182, 101, 212, 80, 173, 138, 180, 156, 252, 109, + 252, 108, 26, 186, 0, 196, 69, 57, 242, 119, 181, 171, 113, + 120, 224, 0, 118, 155, 61, 246, 56, 178, 47 + }; + private static readonly byte[] InvalidPublicKey = + { + 138, 180, 156, 252, 109, 252, 108, 26, 186, 0, + 196, 69, 57, 102, 15, 151, 149, 242, 119, 181, + 171, 113, 120, 224, 0, 118, 155, 61 + }; + + + private static readonly byte[] SerializedMessage = + { + 1, 0, 2, 4, 138, 180, 156, 252, 109, 252, 108, 26, 186, 0, + 196, 69, 57, 102, 15, 151, 149, 242, 119, 181, 171, 113, + 120, 224, 0, 118, 155, 61, 246, 56, 178, 47, 173, 126, 102, + 53, 246, 163, 32, 189, 27, 84, 69, 94, 217, 196, 152, 178, + 198, 116, 124, 160, 230, 94, 226, 141, 220, 221, 119, 21, + 204, 242, 204, 164, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 74, 83, 80, + 248, 93, 200, 130, 214, 20, 165, 86, 114, 120, 138, 41, 109, + 223, 30, 171, 171, 208, 166, 6, 120, 136, 73, 50, 244, 238, + 246, 160, 61, 96, 239, 228, 59, 10, 206, 186, 110, 68, 55, + 160, 108, 50, 58, 247, 220, 116, 182, 121, 237, 126, 42, 184, + 248, 125, 83, 253, 85, 181, 215, 93, 2, 2, 2, 0, 1, 12, 2, 0, 0, 0, + 128, 150, 152, 0, 0, 0, 0, 0, 3, 1, 0, 21, 72, 101, 108, 108, 111, + 32, 102, 114, 111, 109, 32, 83, 111, 108, 46, 78, 101, 116, 32, 58, 41 + }; + private static readonly byte[] SerializedMessageSignature = + { + 234, 147, 144, 17, 200, 57, 8, 154, 139, 86, 156, 12, 7, 143, 144, + 85, 27, 151, 186, 223, 246, 231, 186, 81, 69, 107, 126, 76, 119, + 14, 112, 57, 38, 5, 28, 109, 99, 30, 249, 154, 87, 241, 28, 161, + 178, 165, 146, 73, 179, 4, 71, 133, 203, 145, 125, 252, 200, 249, + 38, 105, 30, 113, 73, 8 + }; + + private const string ExpectedEncodedPublicKey = "ALSzrjtGi8MZGmAZa6ZhtUZq3rwurWuJqWFdgcj9MMFL"; + private const string ExpectedEncodedPrivateKey = "5ZD7ntKtyHrnqMhfSuKBLdqHzT5N3a2aYnCGBcz4N78b84TKpjwQ4QBsapEnpnZFchM7F1BpqDkSuLdwMZwM8hLi"; + + [TestMethod] + public void TestAccountNoKeys() + { + var account = new Account(); + Assert.IsNotNull(account.PrivateKey, "account.PrivateKey != null"); + Assert.IsNotNull(account.PublicKey, "account.PublicKey != null"); + Assert.IsNotNull(account.GetPrivateKey, "account.GetPrivateKey != null"); + Assert.IsNotNull(account.GetPublicKey, "account.GetPublicKey != null"); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void TestAccountInvalidKeys() + { + _ = new Account(InvalidPrivateKey, InvalidPublicKey); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void TestAccountInvalidPrivateKey() + { + _ = new Account(InvalidPrivateKey, PublicKey); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void TestAccountInvalidPublicKey() + { + _ = new Account(PrivateKey, InvalidPublicKey); + } + + [TestMethod] + public void TestAccountGetPrivateKey() + { + var account = new Account(PrivateKey, PublicKey); + CollectionAssert.AreEqual(PrivateKey, account.PrivateKey); + CollectionAssert.AreEqual(PublicKey, account.PublicKey); + Assert.AreEqual(ExpectedEncodedPublicKey, account.GetPublicKey); + } + + [TestMethod] + public void TestAccountGetPublicKey() + { + var account = new Account(PrivateKey, PublicKey); + CollectionAssert.AreEqual(PrivateKey, account.PrivateKey); + CollectionAssert.AreEqual(PublicKey, account.PublicKey); + Assert.AreEqual(ExpectedEncodedPrivateKey, account.GetPrivateKey); + } + + [TestMethod] + public void TestAccountSign() + { + var account = new Account(PrivateKey, PublicKey); + CollectionAssert.AreEqual(PrivateKey, account.PrivateKey); + CollectionAssert.AreEqual(PublicKey, account.PublicKey); + CollectionAssert.AreEqual(SerializedMessageSignature, account.Sign(SerializedMessage)); + } + + [TestMethod] + public void TestAccountVerify() + { + var account = new Account(PrivateKey, PublicKey); + CollectionAssert.AreEqual(PrivateKey, account.PrivateKey); + CollectionAssert.AreEqual(PublicKey, account.PublicKey); + Assert.IsTrue(account.Verify(SerializedMessage, SerializedMessageSignature)); + } + } } \ No newline at end of file diff --git a/test/Solnet.Wallet.Test/Ed25519Bip32Test.cs b/test/Solnet.Wallet.Test/Ed25519Bip32Test.cs index aa763c2f..9dc691d4 100644 --- a/test/Solnet.Wallet.Test/Ed25519Bip32Test.cs +++ b/test/Solnet.Wallet.Test/Ed25519Bip32Test.cs @@ -1,37 +1,37 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using System; - -namespace Solnet.Wallet.Test -{ - [TestClass] - public class Ed25519Bip32Test - { - /// - /// The valid derivation path. - /// - private const string DerivationPath = "m/44'/501'/0'/0'"; - - /// - /// The invalid derivation path. - /// - private const string InvalidDerivationPath = "m44/'501'//0'/0'"; - - private static readonly byte[] SeedWithoutPassphrase = - { - 124,36,217,106,151,19,165,102,96,101,74,81, - 237,254,232,133,28,167,31,35,119,188,66,40, - 101,104,25,103,139,83,57,7,19,215,6,113,22, - 145,107,209,208,107,159,40,223,19,82,53,136, - 255,40,171,137,93,9,205,28,7,207,88,194,91, - 219,232 - }; - - [TestMethod] - [ExpectedException(typeof(FormatException))] - public void TestDerivePath() - { - var ed25519 = new Ed25519Bip32(SeedWithoutPassphrase); - _ = ed25519.DerivePath(InvalidDerivationPath); - } - } +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; + +namespace Solnet.Wallet.Test +{ + [TestClass] + public class Ed25519Bip32Test + { + /// + /// The valid derivation path. + /// + private const string DerivationPath = "m/44'/501'/0'/0'"; + + /// + /// The invalid derivation path. + /// + private const string InvalidDerivationPath = "m44/'501'//0'/0'"; + + private static readonly byte[] SeedWithoutPassphrase = + { + 124,36,217,106,151,19,165,102,96,101,74,81, + 237,254,232,133,28,167,31,35,119,188,66,40, + 101,104,25,103,139,83,57,7,19,215,6,113,22, + 145,107,209,208,107,159,40,223,19,82,53,136, + 255,40,171,137,93,9,205,28,7,207,88,194,91, + 219,232 + }; + + [TestMethod] + [ExpectedException(typeof(FormatException))] + public void TestDerivePath() + { + var ed25519 = new Ed25519Bip32(SeedWithoutPassphrase); + _ = ed25519.DerivePath(InvalidDerivationPath); + } + } } \ No newline at end of file diff --git a/test/Solnet.Wallet.Test/WalletTest.cs b/test/Solnet.Wallet.Test/WalletTest.cs index 95f1bf25..e3883d5c 100644 --- a/test/Solnet.Wallet.Test/WalletTest.cs +++ b/test/Solnet.Wallet.Test/WalletTest.cs @@ -1,254 +1,254 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using NBitcoin; -using System; -using System.Collections.Generic; - -namespace Solnet.Wallet.Test -{ - [TestClass] - public class WalletTest - { - private static readonly byte[] SeedWithoutPassphrase = - { - 124,36,217,106,151,19,165,102,96,101,74,81, - 237,254,232,133,28,167,31,35,119,188,66,40, - 101,104,25,103,139,83,57,7,19,215,6,113,22, - 145,107,209,208,107,159,40,223,19,82,53,136, - 255,40,171,137,93,9,205,28,7,207,88,194,91, - 219,232 - }; - private static readonly byte[] SeedWithPassphrase = - { - 163,4,184,24,182,219,174,214,13,54,158,198, - 63,202,76,3,190,224,76,202,160,96,124,95,89, - 155,113,10,46,218,154,74,125,7,103,78,0,51, - 244,192,221,12,200,148,9,252,4,117,193,123, - 102,56,255,105,167,180,125,222,19,111,219,18, - 115,0 - }; - - private static readonly byte[] SerializedMessage = - { - 1, 0, 2, 4, 138, 180, 156, 252, 109, 252, 108, 26, 186, 0, - 196, 69, 57, 102, 15, 151, 149, 242, 119, 181, 171, 113, - 120, 224, 0, 118, 155, 61, 246, 56, 178, 47, 173, 126, 102, - 53, 246, 163, 32, 189, 27, 84, 69, 94, 217, 196, 152, 178, - 198, 116, 124, 160, 230, 94, 226, 141, 220, 221, 119, 21, - 204, 242, 204, 164, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 74, 83, 80, - 248, 93, 200, 130, 214, 20, 165, 86, 114, 120, 138, 41, 109, - 223, 30, 171, 171, 208, 166, 6, 120, 136, 73, 50, 244, 238, - 246, 160, 61, 96, 239, 228, 59, 10, 206, 186, 110, 68, 55, - 160, 108, 50, 58, 247, 220, 116, 182, 121, 237, 126, 42, 184, - 248, 125, 83, 253, 85, 181, 215, 93, 2, 2, 2, 0, 1, 12, 2, 0, 0, 0, - 128, 150, 152, 0, 0, 0, 0, 0, 3, 1, 0, 21, 72, 101, 108, 108, 111, - 32, 102, 114, 111, 109, 32, 83, 111, 108, 46, 78, 101, 116, 32, 58, 41 - }; - private static readonly byte[] SerializedMessageSignature = - { - 234, 147, 144, 17, 200, 57, 8, 154, 139, 86, 156, 12, 7, 143, 144, - 85, 27, 151, 186, 223, 246, 231, 186, 81, 69, 107, 126, 76, 119, - 14, 112, 57, 38, 5, 28, 109, 99, 30, 249, 154, 87, 241, 28, 161, - 178, 165, 146, 73, 179, 4, 71, 133, 203, 145, 125, 252, 200, 249, - 38, 105, 30, 113, 73, 8 - }; - private static readonly byte[] SerializedMessageSignatureBip39 = - { - 28, 126, 243, 240, 127, 153, 168, 18, 202, 11, 27, 255, 242, 180, 193, 230, 100, - 109, 213, 104, 22, 230, 164, 231, 20, 10, 64, 213, 212, 108, 210, 59, 174, 106, - 61, 254, 120, 250, 15, 109, 254, 142, 88, 176, 145, 111, 0, 231, 29, 225, 10, 193, - 135, 130, 54, 21, 25, 48, 147, 4, 138, 171, 252, 15 - }; - - private const string MnemonicWords = - "lens scheme misery search address destroy shallow police picture gown apart rural cotton vivid cage disagree enrich govern history kit early near cloth alarm"; - private const string Bip39Passphrase = "bip39passphrase"; - - private static readonly Mnemonic Mnemonic = new(MnemonicWords); - - /// - /// Expected key pairs from wallet initialization using the above parameters, as output from sollet.io - /// - private static readonly List<(string PublicKey, string PrivateKey)> ExpectedSolletKeys = new() - { - ("ALSzrjtGi8MZGmAZa6ZhtUZq3rwurWuJqWFdgcj9MMFL", - "5ZD7ntKtyHrnqMhfSuKBLdqHzT5N3a2aYnCGBcz4N78b84TKpjwQ4QBsapEnpnZFchM7F1BpqDkSuLdwMZwM8hLi"), - ("CgFKZ1VLJvip93rh7qKqiGwZjxXb4XXC4GhBGBizuWUb", - "5hTHMuq5vKJachfenfKeAoDhMttXFfN77G51L8KiVRsZqRmzFvNLUdMFDRYgTfuX6yy9g6gCpatzray4XFX5B8xb"), - ("C6jL32xjsGr9fmMdd56TF9oQURN19EfemFxkdpzRoyxm", - "UYhpZrPoRGvHur6ZunZT6VraiTC85NjGsFDrm8LLx3kZkThHEUGSkAuJhn2KUAt2o2Nf3EeFhEW52REzmD3iPgV") - }; - - /// - /// Expected key pair from wallet initialization using the above parameters, as output from solana-keygen cli tool - /// - private const string ExpectedSolanaKeygenPrivateKey = "4G39ryne39vSdXj8v2dVEuN7jMrbMLRD6BtPXydtHoqHHs8SyTAvtjScrzGxvUDo4p6Fz3QaxqF3FUHxn3k68D6M"; - private const string ExpectedSolanaKeygenPublicKey = "4n8BE7DHH4NudifUBrwPbvNPs2F86XcagT7C2JKdrWrR"; - - private static Wallet SetupWalletFromMnemonicWords(SeedMode seedMode) - { - return seedMode switch - { - SeedMode.Bip39 => new Wallet(MnemonicWords, Wordlist.English, Bip39Passphrase, SeedMode.Bip39), - SeedMode.Ed25519Bip32 => new Wallet(MnemonicWords, Wordlist.English), - _ => throw new ArgumentOutOfRangeException(nameof(seedMode), seedMode, "this should never happen") - }; - } - - private static Wallet SetupWalletFromSeed(SeedMode seedMode) - { - return seedMode switch - { - SeedMode.Bip39 => new Wallet(SeedWithPassphrase, Bip39Passphrase, SeedMode.Bip39), - SeedMode.Ed25519Bip32 => new Wallet(SeedWithoutPassphrase), - _ => throw new ArgumentOutOfRangeException(nameof(seedMode), seedMode, "this should never happen") - }; - } - - private static Wallet SetupWalletFromMnemonic(SeedMode seedMode) - { - return seedMode switch - { - SeedMode.Bip39 => new Wallet(Mnemonic, Bip39Passphrase, SeedMode.Bip39), - SeedMode.Ed25519Bip32 => new Wallet(Mnemonic), - _ => throw new ArgumentOutOfRangeException(nameof(seedMode), seedMode, "this should never happen") - }; - } - - [TestMethod] - public void TestWallet() - { - var wallet = new Wallet(WordCount.TwentyFour, Wordlist.English); - Assert.IsNotNull(wallet.Account, "_wallet.account != null"); - Assert.IsNotNull(wallet.Account.PrivateKey, "_wallet.account.PrivateKey != null"); - Assert.IsNotNull(wallet.Account.PublicKey, "_wallet.account.PublicKey != null"); - Assert.IsNotNull(wallet.Account.GetPrivateKey, "_wallet.account.GetPrivateKey != null"); - Assert.IsNotNull(wallet.Account.GetPublicKey, "_wallet.account.GetPublicKey != null"); - - var signature = wallet.Account.Sign(SerializedMessage); - Assert.IsTrue(wallet.Account.Verify(SerializedMessage, signature)); - - var sig = wallet.Sign(SerializedMessage, 2); - Assert.IsTrue(wallet.Verify(SerializedMessage, sig, 2)); - } - - [TestMethod] - public void TestWalletEd25519Bip32FromWords() - { - var wallet = SetupWalletFromMnemonicWords(SeedMode.Ed25519Bip32); - - CollectionAssert.AreEqual(SeedWithoutPassphrase, wallet.DeriveMnemonicSeed()); - - for (var i = 0; i < ExpectedSolletKeys.Count; i++) - { - var account = wallet.GetAccount(i); - Assert.AreEqual(ExpectedSolletKeys[i].PublicKey, account.GetPublicKey); - Assert.AreEqual(ExpectedSolletKeys[i].PrivateKey, account.GetPrivateKey); - } - } - - [TestMethod] - public void TestWalletBip39FromWords() - { - var wallet = SetupWalletFromMnemonicWords(SeedMode.Bip39); - - CollectionAssert.AreEqual(SeedWithPassphrase, wallet.DeriveMnemonicSeed()); - - Assert.AreEqual(ExpectedSolanaKeygenPublicKey, wallet.Account.GetPublicKey); - Assert.AreEqual(ExpectedSolanaKeygenPrivateKey, wallet.Account.GetPrivateKey); - } - - [TestMethod] - public void TestWalletEd25519Bip32FromSeed() - { - var wallet = SetupWalletFromSeed(SeedMode.Ed25519Bip32); - - CollectionAssert.AreEqual(SeedWithoutPassphrase, wallet.DeriveMnemonicSeed()); - - for (var i = 0; i < ExpectedSolletKeys.Count; i++) - { - var account = wallet.GetAccount(i); - Assert.AreEqual(ExpectedSolletKeys[i].PublicKey, account.GetPublicKey); - Assert.AreEqual(ExpectedSolletKeys[i].PrivateKey, account.GetPrivateKey); - } - } - - [TestMethod] - public void TestWalletBip39FromSeed() - { - var wallet = SetupWalletFromSeed(SeedMode.Bip39); - - CollectionAssert.AreEqual(SeedWithPassphrase, wallet.DeriveMnemonicSeed()); - - Assert.AreEqual(ExpectedSolanaKeygenPublicKey, wallet.Account.GetPublicKey); - Assert.AreEqual(ExpectedSolanaKeygenPrivateKey, wallet.Account.GetPrivateKey); - } - - - [TestMethod] - public void TestWalletEd25519Bip32FromMnemonic() - { - var wallet = SetupWalletFromMnemonic(SeedMode.Ed25519Bip32); - - CollectionAssert.AreEqual(SeedWithoutPassphrase, wallet.DeriveMnemonicSeed()); - - for (var i = 0; i < ExpectedSolletKeys.Count; i++) - { - var account = wallet.GetAccount(i); - Assert.AreEqual(ExpectedSolletKeys[i].PublicKey, account.GetPublicKey); - Assert.AreEqual(ExpectedSolletKeys[i].PrivateKey, account.GetPrivateKey); - } - } - - [TestMethod] - public void TestWalletBip39FromMnemonic() - { - var wallet = SetupWalletFromMnemonic(SeedMode.Bip39); - - CollectionAssert.AreEqual(SeedWithPassphrase, wallet.DeriveMnemonicSeed()); - - Assert.AreEqual(ExpectedSolanaKeygenPublicKey, wallet.Account.GetPublicKey); - Assert.AreEqual(ExpectedSolanaKeygenPrivateKey, wallet.Account.GetPrivateKey); - } - - [TestMethod] - public void TestWalletSignEd25519Bip32() - { - var wallet = SetupWalletFromMnemonicWords(SeedMode.Ed25519Bip32); - - CollectionAssert.AreEqual(SerializedMessageSignature, wallet.Account.Sign(SerializedMessage)); - CollectionAssert.AreEqual(SerializedMessageSignature, wallet.GetAccount(0).Sign(SerializedMessage)); - } - - [TestMethod] - public void TestWalletVerifyEd25519Bip32() - { - var wallet = SetupWalletFromMnemonicWords(SeedMode.Ed25519Bip32); - - Assert.IsTrue(wallet.Account.Verify(SerializedMessage, SerializedMessageSignature)); - Assert.IsTrue(wallet.GetAccount(0).Verify(SerializedMessage, SerializedMessageSignature)); - } - - [TestMethod] - public void TestWalletSignBip39() - { - var wallet = SetupWalletFromMnemonicWords(SeedMode.Bip39); - - Assert.ThrowsException(() => wallet.Sign(SerializedMessage, 1)); - Assert.ThrowsException(() => wallet.GetAccount(0).Sign(SerializedMessage)); - - CollectionAssert.AreEqual(SerializedMessageSignatureBip39, wallet.Account.Sign(SerializedMessage)); - } - - [TestMethod] - public void TestWalletVerifyBip39() - { - var wallet = SetupWalletFromMnemonicWords(SeedMode.Bip39); - - Assert.ThrowsException(() => wallet.Verify(SerializedMessage, SerializedMessageSignature, 1)); - Assert.ThrowsException(() => wallet.GetAccount(0).Verify(SerializedMessage, SerializedMessageSignature)); - - Assert.IsTrue(wallet.Account.Verify(SerializedMessage, SerializedMessageSignatureBip39)); - } - } +using Microsoft.VisualStudio.TestTools.UnitTesting; +using NBitcoin; +using System; +using System.Collections.Generic; + +namespace Solnet.Wallet.Test +{ + [TestClass] + public class WalletTest + { + private static readonly byte[] SeedWithoutPassphrase = + { + 124,36,217,106,151,19,165,102,96,101,74,81, + 237,254,232,133,28,167,31,35,119,188,66,40, + 101,104,25,103,139,83,57,7,19,215,6,113,22, + 145,107,209,208,107,159,40,223,19,82,53,136, + 255,40,171,137,93,9,205,28,7,207,88,194,91, + 219,232 + }; + private static readonly byte[] SeedWithPassphrase = + { + 163,4,184,24,182,219,174,214,13,54,158,198, + 63,202,76,3,190,224,76,202,160,96,124,95,89, + 155,113,10,46,218,154,74,125,7,103,78,0,51, + 244,192,221,12,200,148,9,252,4,117,193,123, + 102,56,255,105,167,180,125,222,19,111,219,18, + 115,0 + }; + + private static readonly byte[] SerializedMessage = + { + 1, 0, 2, 4, 138, 180, 156, 252, 109, 252, 108, 26, 186, 0, + 196, 69, 57, 102, 15, 151, 149, 242, 119, 181, 171, 113, + 120, 224, 0, 118, 155, 61, 246, 56, 178, 47, 173, 126, 102, + 53, 246, 163, 32, 189, 27, 84, 69, 94, 217, 196, 152, 178, + 198, 116, 124, 160, 230, 94, 226, 141, 220, 221, 119, 21, + 204, 242, 204, 164, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 74, 83, 80, + 248, 93, 200, 130, 214, 20, 165, 86, 114, 120, 138, 41, 109, + 223, 30, 171, 171, 208, 166, 6, 120, 136, 73, 50, 244, 238, + 246, 160, 61, 96, 239, 228, 59, 10, 206, 186, 110, 68, 55, + 160, 108, 50, 58, 247, 220, 116, 182, 121, 237, 126, 42, 184, + 248, 125, 83, 253, 85, 181, 215, 93, 2, 2, 2, 0, 1, 12, 2, 0, 0, 0, + 128, 150, 152, 0, 0, 0, 0, 0, 3, 1, 0, 21, 72, 101, 108, 108, 111, + 32, 102, 114, 111, 109, 32, 83, 111, 108, 46, 78, 101, 116, 32, 58, 41 + }; + private static readonly byte[] SerializedMessageSignature = + { + 234, 147, 144, 17, 200, 57, 8, 154, 139, 86, 156, 12, 7, 143, 144, + 85, 27, 151, 186, 223, 246, 231, 186, 81, 69, 107, 126, 76, 119, + 14, 112, 57, 38, 5, 28, 109, 99, 30, 249, 154, 87, 241, 28, 161, + 178, 165, 146, 73, 179, 4, 71, 133, 203, 145, 125, 252, 200, 249, + 38, 105, 30, 113, 73, 8 + }; + private static readonly byte[] SerializedMessageSignatureBip39 = + { + 28, 126, 243, 240, 127, 153, 168, 18, 202, 11, 27, 255, 242, 180, 193, 230, 100, + 109, 213, 104, 22, 230, 164, 231, 20, 10, 64, 213, 212, 108, 210, 59, 174, 106, + 61, 254, 120, 250, 15, 109, 254, 142, 88, 176, 145, 111, 0, 231, 29, 225, 10, 193, + 135, 130, 54, 21, 25, 48, 147, 4, 138, 171, 252, 15 + }; + + private const string MnemonicWords = + "lens scheme misery search address destroy shallow police picture gown apart rural cotton vivid cage disagree enrich govern history kit early near cloth alarm"; + private const string Bip39Passphrase = "bip39passphrase"; + + private static readonly Mnemonic Mnemonic = new(MnemonicWords); + + /// + /// Expected key pairs from wallet initialization using the above parameters, as output from sollet.io + /// + private static readonly List<(string PublicKey, string PrivateKey)> ExpectedSolletKeys = new() + { + ("ALSzrjtGi8MZGmAZa6ZhtUZq3rwurWuJqWFdgcj9MMFL", + "5ZD7ntKtyHrnqMhfSuKBLdqHzT5N3a2aYnCGBcz4N78b84TKpjwQ4QBsapEnpnZFchM7F1BpqDkSuLdwMZwM8hLi"), + ("CgFKZ1VLJvip93rh7qKqiGwZjxXb4XXC4GhBGBizuWUb", + "5hTHMuq5vKJachfenfKeAoDhMttXFfN77G51L8KiVRsZqRmzFvNLUdMFDRYgTfuX6yy9g6gCpatzray4XFX5B8xb"), + ("C6jL32xjsGr9fmMdd56TF9oQURN19EfemFxkdpzRoyxm", + "UYhpZrPoRGvHur6ZunZT6VraiTC85NjGsFDrm8LLx3kZkThHEUGSkAuJhn2KUAt2o2Nf3EeFhEW52REzmD3iPgV") + }; + + /// + /// Expected key pair from wallet initialization using the above parameters, as output from solana-keygen cli tool + /// + private const string ExpectedSolanaKeygenPrivateKey = "4G39ryne39vSdXj8v2dVEuN7jMrbMLRD6BtPXydtHoqHHs8SyTAvtjScrzGxvUDo4p6Fz3QaxqF3FUHxn3k68D6M"; + private const string ExpectedSolanaKeygenPublicKey = "4n8BE7DHH4NudifUBrwPbvNPs2F86XcagT7C2JKdrWrR"; + + private static Wallet SetupWalletFromMnemonicWords(SeedMode seedMode) + { + return seedMode switch + { + SeedMode.Bip39 => new Wallet(MnemonicWords, Wordlist.English, Bip39Passphrase, SeedMode.Bip39), + SeedMode.Ed25519Bip32 => new Wallet(MnemonicWords, Wordlist.English), + _ => throw new ArgumentOutOfRangeException(nameof(seedMode), seedMode, "this should never happen") + }; + } + + private static Wallet SetupWalletFromSeed(SeedMode seedMode) + { + return seedMode switch + { + SeedMode.Bip39 => new Wallet(SeedWithPassphrase, Bip39Passphrase, SeedMode.Bip39), + SeedMode.Ed25519Bip32 => new Wallet(SeedWithoutPassphrase), + _ => throw new ArgumentOutOfRangeException(nameof(seedMode), seedMode, "this should never happen") + }; + } + + private static Wallet SetupWalletFromMnemonic(SeedMode seedMode) + { + return seedMode switch + { + SeedMode.Bip39 => new Wallet(Mnemonic, Bip39Passphrase, SeedMode.Bip39), + SeedMode.Ed25519Bip32 => new Wallet(Mnemonic), + _ => throw new ArgumentOutOfRangeException(nameof(seedMode), seedMode, "this should never happen") + }; + } + + [TestMethod] + public void TestWallet() + { + var wallet = new Wallet(WordCount.TwentyFour, Wordlist.English); + Assert.IsNotNull(wallet.Account, "_wallet.account != null"); + Assert.IsNotNull(wallet.Account.PrivateKey, "_wallet.account.PrivateKey != null"); + Assert.IsNotNull(wallet.Account.PublicKey, "_wallet.account.PublicKey != null"); + Assert.IsNotNull(wallet.Account.GetPrivateKey, "_wallet.account.GetPrivateKey != null"); + Assert.IsNotNull(wallet.Account.GetPublicKey, "_wallet.account.GetPublicKey != null"); + + var signature = wallet.Account.Sign(SerializedMessage); + Assert.IsTrue(wallet.Account.Verify(SerializedMessage, signature)); + + var sig = wallet.Sign(SerializedMessage, 2); + Assert.IsTrue(wallet.Verify(SerializedMessage, sig, 2)); + } + + [TestMethod] + public void TestWalletEd25519Bip32FromWords() + { + var wallet = SetupWalletFromMnemonicWords(SeedMode.Ed25519Bip32); + + CollectionAssert.AreEqual(SeedWithoutPassphrase, wallet.DeriveMnemonicSeed()); + + for (var i = 0; i < ExpectedSolletKeys.Count; i++) + { + var account = wallet.GetAccount(i); + Assert.AreEqual(ExpectedSolletKeys[i].PublicKey, account.GetPublicKey); + Assert.AreEqual(ExpectedSolletKeys[i].PrivateKey, account.GetPrivateKey); + } + } + + [TestMethod] + public void TestWalletBip39FromWords() + { + var wallet = SetupWalletFromMnemonicWords(SeedMode.Bip39); + + CollectionAssert.AreEqual(SeedWithPassphrase, wallet.DeriveMnemonicSeed()); + + Assert.AreEqual(ExpectedSolanaKeygenPublicKey, wallet.Account.GetPublicKey); + Assert.AreEqual(ExpectedSolanaKeygenPrivateKey, wallet.Account.GetPrivateKey); + } + + [TestMethod] + public void TestWalletEd25519Bip32FromSeed() + { + var wallet = SetupWalletFromSeed(SeedMode.Ed25519Bip32); + + CollectionAssert.AreEqual(SeedWithoutPassphrase, wallet.DeriveMnemonicSeed()); + + for (var i = 0; i < ExpectedSolletKeys.Count; i++) + { + var account = wallet.GetAccount(i); + Assert.AreEqual(ExpectedSolletKeys[i].PublicKey, account.GetPublicKey); + Assert.AreEqual(ExpectedSolletKeys[i].PrivateKey, account.GetPrivateKey); + } + } + + [TestMethod] + public void TestWalletBip39FromSeed() + { + var wallet = SetupWalletFromSeed(SeedMode.Bip39); + + CollectionAssert.AreEqual(SeedWithPassphrase, wallet.DeriveMnemonicSeed()); + + Assert.AreEqual(ExpectedSolanaKeygenPublicKey, wallet.Account.GetPublicKey); + Assert.AreEqual(ExpectedSolanaKeygenPrivateKey, wallet.Account.GetPrivateKey); + } + + + [TestMethod] + public void TestWalletEd25519Bip32FromMnemonic() + { + var wallet = SetupWalletFromMnemonic(SeedMode.Ed25519Bip32); + + CollectionAssert.AreEqual(SeedWithoutPassphrase, wallet.DeriveMnemonicSeed()); + + for (var i = 0; i < ExpectedSolletKeys.Count; i++) + { + var account = wallet.GetAccount(i); + Assert.AreEqual(ExpectedSolletKeys[i].PublicKey, account.GetPublicKey); + Assert.AreEqual(ExpectedSolletKeys[i].PrivateKey, account.GetPrivateKey); + } + } + + [TestMethod] + public void TestWalletBip39FromMnemonic() + { + var wallet = SetupWalletFromMnemonic(SeedMode.Bip39); + + CollectionAssert.AreEqual(SeedWithPassphrase, wallet.DeriveMnemonicSeed()); + + Assert.AreEqual(ExpectedSolanaKeygenPublicKey, wallet.Account.GetPublicKey); + Assert.AreEqual(ExpectedSolanaKeygenPrivateKey, wallet.Account.GetPrivateKey); + } + + [TestMethod] + public void TestWalletSignEd25519Bip32() + { + var wallet = SetupWalletFromMnemonicWords(SeedMode.Ed25519Bip32); + + CollectionAssert.AreEqual(SerializedMessageSignature, wallet.Account.Sign(SerializedMessage)); + CollectionAssert.AreEqual(SerializedMessageSignature, wallet.GetAccount(0).Sign(SerializedMessage)); + } + + [TestMethod] + public void TestWalletVerifyEd25519Bip32() + { + var wallet = SetupWalletFromMnemonicWords(SeedMode.Ed25519Bip32); + + Assert.IsTrue(wallet.Account.Verify(SerializedMessage, SerializedMessageSignature)); + Assert.IsTrue(wallet.GetAccount(0).Verify(SerializedMessage, SerializedMessageSignature)); + } + + [TestMethod] + public void TestWalletSignBip39() + { + var wallet = SetupWalletFromMnemonicWords(SeedMode.Bip39); + + Assert.ThrowsException(() => wallet.Sign(SerializedMessage, 1)); + Assert.ThrowsException(() => wallet.GetAccount(0).Sign(SerializedMessage)); + + CollectionAssert.AreEqual(SerializedMessageSignatureBip39, wallet.Account.Sign(SerializedMessage)); + } + + [TestMethod] + public void TestWalletVerifyBip39() + { + var wallet = SetupWalletFromMnemonicWords(SeedMode.Bip39); + + Assert.ThrowsException(() => wallet.Verify(SerializedMessage, SerializedMessageSignature, 1)); + Assert.ThrowsException(() => wallet.GetAccount(0).Verify(SerializedMessage, SerializedMessageSignature)); + + Assert.IsTrue(wallet.Account.Verify(SerializedMessage, SerializedMessageSignatureBip39)); + } + } } \ No newline at end of file