Solana.KeyStore
10.0.0
dotnet add package Solana.KeyStore --version 10.0.0
NuGet\Install-Package Solana.KeyStore -Version 10.0.0
<PackageReference Include="Solana.KeyStore" Version="10.0.0" />
<PackageVersion Include="Solana.KeyStore" Version="10.0.0" />
<PackageReference Include="Solana.KeyStore" />
paket add Solana.KeyStore --version 10.0.0
#r "nuget: Solana.KeyStore, 10.0.0"
#:package Solana.KeyStore@10.0.0
#addin nuget:?package=Solana.KeyStore&version=10.0.0
#tool nuget:?package=Solana.KeyStore&version=10.0.0
<p align="center"> <img src="https://raw.githubusercontent.com/bmresearch/Solnet/master/assets/icon.png" margin="auto" height="175"/> </p>
<p align="center"> <a href="https://github.com/bmresearch/Solnet/actions/workflows/dotnet.yml"> <img src="https://github.com/bmresearch/Solnet/actions/workflows/dotnet.yml/badge.svg" alt="Build" ></a> <br/> <a href=""> <img src="https://img.shields.io/github/license/bmresearch/Solnet?style=flat-square" alt="Code License"></a> <a href="https://twitter.com/intent/follow?screen_name=blockmountainio"> <img src="https://img.shields.io/twitter/follow/blockmountainio?style=flat-square&logo=twitter" alt="Follow on Twitter"></a> <a href="https://discord.gg/cReXaBReZt"> <img alt="Discord" src="https://img.shields.io/discord/849407317761064961?style=flat-square" alt="Join the discussion!"></a> </p>
<div style="text-align:center">
<p>
Introduction
Solnet is Solana's C# SDK designed to integrate seamlessly with the .NET ecosystem for web, mobile, and desktop apps. Whether you're a seasoned developer or just getting started, you'll find examples, docs, and APIs that make Solana development in .NET straightforward.
</p>
</div>
Features
- Full JSON RPC API coverage
- Full Streaming JSON RPC API coverage
- Wallet and accounts (Phantom and solana-keygen compatible)
- Keystore (Phantom and solana-keygen compatible)
- Transaction decoding/encoding (base64 and wire format)
- Message decoding/encoding (base64 and wire format)
- Instruction decompilation
- TokenWallet object to send SPL tokens and JIT provisioning of Associated Token Accounts
- Programs
- Native Programs
- System Program
- Stake Program
- Solana Program Library (SPL)
- Compute Budget Program
- Account Compression Program
- Governance Program
- StakePool Program
- Address Lookup Table Program
- Memo Program
- Token Program
- Token Swap Program
- Associated Token Account Program
- Name Service Program
- Shared Memory Program
- Native Programs
For maintainability and due to the size/complexity of some other programs, this repository focuses on Solana's Native Programs and SPL programs. For other commonly used programs see:
Maintained by Bifrost <img src="https://avatars.githubusercontent.com/u/119550733?s=64&v=4" width=25 />
Requirements
- .NET 6.0 (deprecated)
- .NET 8.0+ (recommended)
Dependencies
- BifrostSecurity
- Portable.BouncyCastle (optional)
Installation
Use the .NET CLI to install Solnet packages into your project (choose the ones you need):
# Rpc client and streaming
dotnet add package Solana.Rpc
dotnet add package Solana.Programs
# Wallets and keys
dotnet add package Solana.Wallet
# Token helpers
dotnet add package Solana.Extensions
# Keystore utilities
dotnet add package Solana.Keystore
Latest Packages on NuGet:
Quickstart
A minimal example to fetch a balance and send a memo:
using Solnet.Rpc;
using Solnet.Rpc.Builders;
using Solnet.Rpc.Types;
using Solnet.Programs;
using Solnet.Wallet;
var rpc = ClientFactory.GetClient(Cluster.MainNet);
var wallet = new Wallet();
var from = wallet.GetAccount(0);
// Get balance
var bal = rpc.GetBalance(from.PublicKey);
Console.WriteLine($"Balance: {bal.Result.Value} lamports");
// Send a simple memo transaction
var blockhash = rpc.GetLatestBlockHash();
var tx = new TransactionBuilder()
.SetRecentBlockHash(blockhash.Result.Value.Blockhash)
.SetFeePayer(from)
.AddInstruction(ComputeBudgetProgram.SetComputeUnitLimit(30000))
.AddInstruction(ComputeBudgetProgram.SetComputeUnitPrice(1000000))
.AddInstruction(MemoProgram.NewMemo(from, "Hello from Solnet"))
.Build(from);
var sig = rpc.SendTransaction(tx);
Console.WriteLine($"tx: {sig.Result}");
Examples
The Solnet.Examples project contains runnable code examples. Some examples derive accounts from a seed and may fail if an account at a derivation index already exists with the same seed. If you see simulation logs like account address is ... already in use, increment the derivation index (e.g., wallet.GetAccount(index + 1)).
Wallets
The Solnet.Wallet project implements wallet and key generation compatible with both solana-keygen and Phantom.
Initialize a keypair from a secret key
var account = Account.FromSecretKey("");
Initialize a wallet (Phantom-compatible by default)
var wallet = new Wallet("mnemonic words ...", WordList.English);
var account = wallet.GetAccount(10);
Initialize a wallet compatible with solana-keygen
var wallet = new Wallet("mnemonic words ...", WordList.English, "passphrase", SeedMode.Bip39);
var account = wallet.Account; // solana-keygen uses a fixed derivation path
Generating new wallets
var newMnemonic = new Mnemonic(WordList.English, WordCount.Twelve);
var wallet = new Wallet(newMnemonic);
KeyStore
The Solnet.KeyStore project enables secure storage of keys, seeds, and mnemonics. It implements the Web3 Secret Storage Definition and includes SolanaKeyStoreService to read keys generated by solana-keygen.
Secret KeyStore Service
var secretKeyStoreService = new SecretKeyStoreService();
var jsonString = secretKeyStoreService.EncryptAndGenerateDefaultKeyStoreAsJson(password, data, address);
try
{
var decrypted = KeyStore.DecryptKeyStoreFromJson(password, jsonString);
}
catch (Exception)
{
Console.WriteLine("Invalid password!");
}
Solana KeyStore Service
var solanaKeyStoreService = new SolanaKeyStoreService();
var wallet = solanaKeyStoreService.RestoreKeystore(filePath, passphrase);
RPC and Streaming RPC
The Solnet.Rpc project contains a full implementation of the Solana JSON RPC.
ClientFactory pattern
var rpcClient = ClientFactory.GetClient(Cluster.MainNet, logger);
var paidClient = ClientFactory.GetClient("https://your.paid.rpc", logger);
var streamingRpcClient = ClientFactory.GetStreamingClient(Cluster.MainNet, logger);
Using the RPC
var accountInfo = rpcClient.GetAccountInfo("5omQJtDUHA3gMFdHEQg1zZSvcBUVzey5WaKWYRmqF1Vj");
var tokenAccounts = rpcClient.GetTokenAccountsByOwner("5omQJtDUHA3gMFdHEQg1zZSvcBUVzey5WaKWYRmqF1Vj");
var wrappedSolAccounts = rpcClient.GetTokenAccountsByOwner(
"5omQJtDUHA3gMFdHEQg1zZSvcBUVzey5WaKWYRmqF1Vj",
"So11111111111111111111111111111111111111112");
var serumAddress = "9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin";
var programAccounts = rpcClient.GetProgramAccounts(serumAddress);
var filters = new List<MemCmp>() { new MemCmp { Offset = 45, Bytes = OwnerAddress } };
var filteredProgramAccounts = rpcClient.GetProgramAccounts(serumAddress, memCmpList: filters);
Using the Streaming RPC
var txSig = rpcClient.SendTransaction(tx);
var subscription = streamingRpcClient.SubscribeSignature(txSig.Result, (state, response) =>
{
// handle confirmation
}, Commitment.Finalized);
Sending a transaction
Important: Understanding priority fees
Poorly optimized transactions can get dropped due to high compute demand. Always specify both compute unit limit and price to improve inclusion probability.
var txBuilder = new TransactionBuilder()
.AddInstruction(ComputeBudgetProgram.SetComputeUnitLimit(30000))
.AddInstruction(ComputeBudgetProgram.SetComputeUnitPrice(1000000));
var rpcClient = ClientFactory.GetClient(Cluster.MainNet);
var wallet = new Wallet();
var fromAccount = wallet.GetAccount(0);
var toAccount = wallet.GetAccount(1);
var blockHash = rpcClient.GetLatestBlockHash();
var tx = new TransactionBuilder()
.SetRecentBlockHash(blockHash.Result.Value.Blockhash)
.SetFeePayer(fromAccount)
.AddInstruction(ComputeBudgetProgram.SetComputeUnitLimit(30000))
.AddInstruction(ComputeBudgetProgram.SetComputeUnitPrice(1000000))
.AddInstruction(MemoProgram.NewMemo(fromAccount, "Hello from Sol.Net :)"))
.AddInstruction(SystemProgram.Transfer(fromAccount, toAccount.PublicKey, 100000))
.Build(fromAccount);
var firstSig = rpcClient.SendTransaction(tx);
Create, Initialize and Mint
var wallet = new Wallet(MnemonicWords);
var blockHash = rpcClient.GetLatestBlockHash();
var minBalanceForExemptionAcc = rpcClient.GetMinimumBalanceForRentExemption(TokenProgram.TokenAccountDataSize).Result;
var minBalanceForExemptionMint = rpcClient.GetMinimumBalanceForRentExemption(TokenProgram.MintAccountDataSize).Result;
var mintAccount = wallet.GetAccount(21);
var ownerAccount = wallet.GetAccount(10);
var initialAccount = wallet.GetAccount(22);
var tx = new TransactionBuilder()
.SetRecentBlockHash(blockHash.Result.Value.Blockhash)
.SetFeePayer(ownerAccount)
.AddInstruction(ComputeBudgetProgram.SetComputeUnitLimit(30000))
.AddInstruction(ComputeBudgetProgram.SetComputeUnitPrice(1000000))
.AddInstruction(SystemProgram.CreateAccount(
ownerAccount,
mintAccount,
minBalanceForExemptionMint,
TokenProgram.MintAccountDataSize,
TokenProgram.ProgramIdKey))
.AddInstruction(TokenProgram.InitializeMint(
mintAccount.PublicKey,
2,
ownerAccount.PublicKey,
ownerAccount.PublicKey))
.AddInstruction(SystemProgram.CreateAccount(
ownerAccount,
initialAccount,
minBalanceForExemptionAcc,
TokenProgram.TokenAccountDataSize,
TokenProgram.ProgramIdKey))
.AddInstruction(TokenProgram.InitializeAccount(
initialAccount.PublicKey,
mintAccount.PublicKey,
ownerAccount.PublicKey))
.AddInstruction(TokenProgram.MintTo(
mintAccount.PublicKey,
initialAccount.PublicKey,
25000,
ownerAccount))
.AddInstruction(MemoProgram.NewMemo(initialAccount, "Hello from Sol.Net"))
.Build(new List<Account>{ ownerAccount, mintAccount, initialAccount });
Transfer a Token to a new Token Account
var rpcClient = ClientFactory.GetClient(Cluster.MainNet);
var wallet = new Wallet();
var blockHash = rpcClient.GetLatestBlockHash();
var minBalanceForExemptionAcc =
rpcClient.GetMinimumBalanceForRentExemption(TokenProgram.TokenAccountDataSize).Result;
var mintAccount = wallet.GetAccount(21);
var ownerAccount = wallet.GetAccount(10);
var initialAccount = wallet.GetAccount(22);
var newAccount = wallet.GetAccount(23);
var tx = new TransactionBuilder()
.SetRecentBlockHash(blockHash.Result.Value.Blockhash)
.SetFeePayer(ownerAccount)
.AddInstruction(ComputeBudgetProgram.SetComputeUnitLimit(30000))
.AddInstruction(ComputeBudgetProgram.SetComputeUnitPrice(1000000))
.AddInstruction(SystemProgram.CreateAccount(
ownerAccount,
newAccount,
minBalanceForExemptionAcc,
TokenProgram.TokenAccountDataSize,
TokenProgram.ProgramIdKey))
.AddInstruction(TokenProgram.InitializeAccount(
newAccount.PublicKey,
mintAccount.PublicKey,
ownerAccount.PublicKey))
.AddInstruction(TokenProgram.Transfer(
initialAccount.PublicKey,
newAccount.PublicKey,
25000,
ownerAccount))
.AddInstruction(MemoProgram.NewMemo(initialAccount, "Hello from Sol.Net"))
.Build(new List<Account>{ ownerAccount, newAccount });
Transaction and Message decoding
var tx = Transaction.Deserialize(txData);
var msg = Message.Deserialize(msgData);
var signedTx = Transaction.Populate(msg, new List<byte[]> { account.Sign(msgData) });
Programs
The Solnet.Programs project contains implementations of several Native and SPL programs. See Solnet.Examples for more depth, including multi-signature operations.
Hello Solana World
var memoInstruction = MemoProgram.NewMemo(wallet.Account, "Hello Solana World, using Solnet :)");
var recentHash = rpcClient.GetLatestBlockHash();
var tx = new TransactionBuilder()
.SetFeePayer(wallet.Account)
.AddInstruction(ComputeBudgetProgram.SetComputeUnitLimit(30000))
.AddInstruction(ComputeBudgetProgram.SetComputeUnitPrice(1000000))
.AddInstruction(memoInstruction)
.SetRecentBlockHash(recentHash.Result.Value.Blockhash)
.Build(wallet.Account);
Creating and sending tokens to an Associated Token Account
var recentHash = rpcClient.GetLatestBlockHash();
PublicKey associatedTokenAccountOwner = new ("65EoWs57dkMEWbK4TJkPDM76rnbumq7r3fiZJnxggj2G");
PublicKey associatedTokenAccount =
AssociatedTokenAccountProgram.DeriveAssociatedTokenAccount(associatedTokenAccountOwner, mintAccount);
byte[] txBytes = new TransactionBuilder()
.SetRecentBlockHash(recentHash.Result.Value.Blockhash)
.SetFeePayer(ownerAccount)
.AddInstruction(ComputeBudgetProgram.SetComputeUnitLimit(30000))
.AddInstruction(ComputeBudgetProgram.SetComputeUnitPrice(1000000))
.AddInstruction(AssociatedTokenAccountProgram.CreateAssociatedTokenAccount(
ownerAccount,
associatedTokenAccountOwner,
mintAccount))
.AddInstruction(TokenProgram.Transfer(
initialAccount,
associatedTokenAccount,
25000,
ownerAccount))
.AddInstruction(MemoProgram.NewMemo(ownerAccount, "Hello from Sol.Net"))
.Build(new List<Account> { ownerAccount });
string signature = rpcClient.SendTransaction(txBytes);
Creating and extending an Address Lookup Table for a complex transaction
var rpcClient = ClientFactory.GetClient(Cluster.MainNet);
var wallet = new Wallet("mnemonic words ...", WordList.English);
var authorityAccount = wallet.GetAccount(0);
var payerAccount = authorityAccount;
// ALT creation depends on a recent slot and derives the lookup table PDA from
// authority + recentSlot.
ulong recentSlot = rpcClient.GetSlot().Result;
PublicKey lookupTableAddress = AddressLookupTableProgram.DeriveLookupTableAddress(
authorityAccount.PublicKey,
recentSlot);
var createBlockHash = rpcClient.GetLatestBlockHash();
byte[] createAltTx = new TransactionBuilder()
.SetRecentBlockHash(createBlockHash.Result.Value.Blockhash)
.SetFeePayer(payerAccount)
.AddInstruction(ComputeBudgetProgram.SetComputeUnitLimit(60000))
.AddInstruction(ComputeBudgetProgram.SetComputeUnitPrice(1000000))
.AddInstruction(AddressLookupTableProgram.CreateLookupTable(
authorityAccount.PublicKey,
payerAccount.PublicKey,
recentSlot))
.Build(payerAccount);
string createAltSignature = rpcClient.SendTransaction(createAltTx).Result;
// Wait for confirmation before extending or consuming the table in a v0 transaction.
List<PublicKey> addressesToCache = new()
{
TokenProgram.ProgramIdKey,
SystemProgram.ProgramIdKey,
new PublicKey("So11111111111111111111111111111111111111112"),
new PublicKey("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL")
};
var extendBlockHash = rpcClient.GetLatestBlockHash();
byte[] extendAltTx = new TransactionBuilder()
.SetRecentBlockHash(extendBlockHash.Result.Value.Blockhash)
.SetFeePayer(payerAccount)
.AddInstruction(ComputeBudgetProgram.SetComputeUnitLimit(60000))
.AddInstruction(ComputeBudgetProgram.SetComputeUnitPrice(1000000))
.AddInstruction(AddressLookupTableProgram.ExtendLookupTable(
lookupTableAddress,
authorityAccount.PublicKey,
payerAccount.PublicKey,
addressesToCache))
.Build(payerAccount);
string extendAltSignature = rpcClient.SendTransaction(extendAltTx).Result;
// Once confirmed, the table can be referenced from a v0 transaction through
// VersionedTransaction.AddressTableLookups and MessageAddressTableLookup.
var lookupReference = new Message.MessageAddressTableLookup
{
AccountKey = lookupTableAddress,
WritableIndexes = System.Array.Empty<byte>(),
ReadonlyIndexes = new byte[] { 0, 1, 2, 3 }
};
This pattern is useful when a later transaction needs more account addresses than fit comfortably in the legacy message format. In Solnet, you typically create and populate the ALT with TransactionBuilder, wait for confirmation, and then reference it from a VersionedTransaction when building the larger v0 message.
v0 Transaction Execution
using static Solnet.Rpc.Models.Message;
var rpc = ClientFactory.GetClient(Cluster.MainNet, logger: LoggerFactory.Create(builder => builder.AddConsole()).CreateLogger("Solnet"));
var wallet = Account.FromSecretKey("YOUR_SECRET_KEY"); // Replace with your
var from = wallet;
// Get balance
var bal = rpc.GetBalance(from.PublicKey);
Console.WriteLine($"Balance: {bal.Result.Value} lamports");
// Create a transaction with a custom TransactionConfig
var alt = new List<MessageAddressLookupTable>();
alt!.Add(new MessageAddressLookupTable { AccountKey = new PublicKey("PUBLIC_KEY"), ReadonlyIndexes = { }, WritableIndexes = { } }); // Add your lookup tables here
var blockhash = rpc.GetLatestBlockHash();
var tx = new TransactionBuilder(TransactionVersion.V0)
.SetRecentBlockHash(blockhash.Result.Value.Blockhash)
.SetFeePayer(from)
.SetAddressLookupTable(alt)
.AddInstruction(ComputeBudgetProgram.SetComputeUnitLimit(1_000_000))
.AddInstruction(ComputeBudgetProgram.SetComputeUnitPrice(10_000))
.AddInstruction(SystemProgram.Transfer(wallet.PublicKey, wallet.PublicKey, 1000)) // Sending 1000 lamports to self
.Build(from);
var sig = rpc.SendTransaction(tx);
Console.WriteLine($"tx: {sig.Result}");
Console.WriteLine($"link: https://explorer.solana.com/tx/{sig.Result}?cluster=mainnet");
var _tx = VersionedTransaction.Deserialize(tx);
var _msg = Message.VersionedMessage.Deserialize(_tx.CompileMessage());
v1 Transaction Execution
v1 Transactions no longer require the computebudget program, but instead require TransactionConfig. For more details on v1 can be found here: Sending v1 Transactions
var rpc = ClientFactory.GetClient(Cluster.MainNet, logger: LoggerFactory.Create(builder => builder.AddConsole()).CreateLogger("Solnet"));
var wallet = Account.FromSecretKey("YOUR_SECRET_KEY");
var from = wallet;
// Get balance
var bal = rpc.GetBalance(from.PublicKey);
Console.WriteLine($"Balance: {bal.Result.Value} lamports");
// Create a transaction with a custom TransactionConfig
var config = new TransactionConfig { ComputeUnitLimit = 20000, PriorityFee = 5000, HeapSize = (64 * 1024), LoadedAccountsDataSizeLimit = (64 * 1024) };
var blockhash = rpc.GetLatestBlockHash();
var tx = new TransactionBuilder(TransactionVersion.V1)
.SetRecentBlockHash(blockhash.Result.Value.Blockhash)
.SetFeePayer(from)
.SetTransactionConfig(config)
.AddInstruction(SystemProgram.Transfer(wallet.PublicKey, wallet.PublicKey, 1000)) // Sending 1000 lamports to self
.Build(from);
var sig = rpc.SendTransaction(tx);
Console.WriteLine($"tx: {sig.Result}");
Console.WriteLine($"link: https://explorer.solana.com/tx/{sig.Result}?cluster=mainnet");
var _tx = VersionedTransaction.Deserialize(tx);
var _msg = Message.VersionedMessage.Deserialize(_tx.CompileMessage());
Console.WriteLine("Deserialize Tx ComputeUnitLimit:" + _tx.TransactionConfig.ComputeUnitLimit!.Value);
Console.WriteLine("Deserialize Tx PriorityFee:" + _tx.TransactionConfig.PriorityFee!.Value);
Console.WriteLine("Deserialize Msg Version:" + _msg.Version);
Instruction decoding
var msg = Message.Deserialize(msgBase64);
var decodedInstructions = InstructionDecoder.DecodeInstructions(msg);
Contribution
We encourage everyone to contribute, submit issues and PRs, and join discussions. Every kind of help is welcome.
Legacy Maintainers
Current Maintainers
- Nathan - BifrostTitan
See also the list of contributors who participated in this project.
License
This project is licensed under the MIT License - see the LICENSE file for details.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- BifrostSecurity (>= 1.0.1)
- Portable.BouncyCastle (>= 1.9.0)
- Solana.Wallet (>= 10.0.0)
NuGet packages (2)
Showing the top 2 NuGet packages that depend on Solana.KeyStore:
| Package | Downloads |
|---|---|
|
Solana.Moonshot
C# SDK for Dexscreener's Moonshot program |
|
|
xiaobei_Solnet.JupiterPerps
原Solnet.JupiterPerps |
GitHub repositories
This package is not used by any popular GitHub repositories.