A lean, modern .NET SDK for Solana — RPC, WebSocket streaming, and wire-level transaction signing and building.
SolSharp is built for low latency and a small dependency footprint. It is a focused, hackable alternative to the heavier general-purpose SDKs: you get direct control over the wire format and the signing path, without dragging in a large dependency graph. If you are writing bots, indexers, or backend services that talk to Solana from .NET and care about speed and control, this is aimed at you.
Status: 0.1.0 — first stable release. SolSharp ships as a single NuGet package —
SolSharp— bundling the Core (primitives + encodings), Wallet (Ed25519 keys, signing, verification), Rpc (HTTP reads + send/simulate + WebSocket streaming + DI), and Programs (instructions + transaction building + signing) assemblies. Versioning follows semver; while on 0.x, minor releases may still carry breaking changes.
📖 New here? Read the usage guide — a task-oriented cookbook covering keys, reads, SPL token state, building/signing/sending transactions, v0 + address lookup tables, decoding transactions, WebSocket subscriptions, confirmation, and more.
When this was started, the .NET options for Solana were either unmaintained and stale or heavy and not built for performance — there was no modern, fast, actively-developed client. SolSharp is a from-scratch answer to that: current C#, allocation-conscious, and tuned for latency-sensitive workloads.
- Lean. No kitchen-sink dependency graph.
Coredepends on a single package (base58). - Wire-level control. Hand-rolled, spec-accurate transaction and message encoding — the part most SDKs hide — with Ed25519 signing on a vetted crypto library, all tested against known vectors.
- Latency-minded. Value types, allocation-free hot paths, span-based APIs.
- Modern .NET. C# latest, nullable reference types, code style enforced on build.
SolSharp ships as a single NuGet package — SolSharp — so one dotnet add package SolSharp pulls in
everything. Internally it stays four layered assemblies, bundled into that one package (namespaces are
unchanged: SolSharp.Core.*, SolSharp.Rpc, SolSharp.Wallet, SolSharp.Programs):
Install from NuGet:
dotnet add package SolSharp<PackageReference Include="SolSharp" Version="0.1.0" />| Assembly | Purpose | Status |
|---|---|---|
SolSharp.Core |
Primitives, encoding, JSON, program/sysvar constants | Usable |
SolSharp.Wallet |
Ed25519 keys, key parsing, signing and verification | Usable |
SolSharp.Rpc |
HTTP JSON-RPC reads + WebSocket streaming + DI | Usable |
SolSharp.Programs |
Instructions (System/Token/ATA/Memo/Compute Budget/ALT) + transaction building | Usable |
Keeping the split in the source means the layering stays compiler-enforced — dependencies point downward
only: Rpc and Wallet build on Core, and Programs builds on Core and Wallet. Core depends on
nothing else in the solution and pulls no network or crypto package.
SolSharp.Core:
PublicKey— a 32-byte value type with value equality, base58 parsing, and JSON support.Base58,ShortVec(compact-u16), andBorshReader/BorshWriter— the encodings Solana uses on the wire, plus a bounds-checked reader and writer for Anchor / Borsh account data and instruction arguments.Commitment— an RPC enum that serializes to its exact wire string.SolanaProgramIds,Sysvars,Mints— well-known on-chain addresses, guarded by a test that every constant decodes to a valid 32-byte key.SolanaUnits— SOL ↔ lamports conversion.
using SolSharp.Core.Primitives;
var mint = PublicKey.Parse("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");
byte[] raw = mint.ToBytes(); // 32 bytes, allocation-free storage
bool ok = PublicKey.TryParse(input, out var key);SolSharp.Rpc:
- HTTP JSON-RPC reads — accounts (
getAccountInfo,getMultipleAccounts,getProgramAccountswith memcmp / data-size filters and data slices,getTokenAccountsByOwner,getTokenLargestAccounts,getTokenAccountBalance,getAddressLookupTablefetch + decode), transactions and blocks (getTransaction,getSignaturesForAddress,getSignatureStatuses,getBlock,getBlockHeight,getTransactionCount,getFeeForMessage), and cluster state (getBalance,getSlot,getLatestBlockhash,isBlockhashValid,getEpochInfo,getVersion,getHealth,getSupply,getSlotLeaders,getRecentPrioritizationFees,getTokenSupply,getMinimumBalanceForRentExemption,requestAirdrop); each typed, fully documented, and tested. - Account-state decoders —
MintandTokenAccount(SPL Token state, viaGetMintAsync/GetTokenAccountAsync) andAddressLookupTable; for other programs, pairgetAccountInfowith Core'sBorshReader. getTransactionreturns the decoded transaction bytes (feed toTransaction.Deserialize) alongside rich metadata — pre/post SOL and token balances, inner (CPI) instructions, loaded lookup-table addresses, logs, and compute units. Failures decode to a typedTransactionError(exposing the program'sCustomcode) onTransactionMeta,SignatureStatus, andSimulateTransactionResult.- WebSocket streaming multiplexed over one connection:
SubscribeSlotsAsyncandSubscribeRootsAsync(IAsyncEnumerable),SubscribeLogsAsync,SubscribeAccountAsync,SubscribeProgramAsync,SubscribeSignatureAsync, andSubscribeBlocksAsync(ChannelReader), with automatic reconnect and resubscribe across dropped connections. - DI registration with a built-in resilience pipeline (retry on transient errors and HTTP 429).
SendTransactionAsync/SimulateTransactionAsync— submit a signed transaction or dry-run it for logs and compute units;SendAndConfirmTransactionAsyncsends and waits for confirmation (throwing if the transaction lands but errors). Confirm by polling (GetSignatureStatusesAsync/ConfirmTransactionAsync) or over the WebSocket (SolanaWsClient.ConfirmSignatureAsync).
using SolSharp.Rpc;
// typed client with retries; tune the pipeline via the optional callback
services.AddSolanaRpc("https://your-rpc-endpoint");
// injected SolanaRpcClient
var lamports = await rpc.GetBalanceAsync(account);
// streaming
await using var ws = new SolanaWsClient();
await ws.ConnectAsync(new Uri("wss://your-rpc-endpoint"));
await foreach (var slot in ws.SubscribeSlotsAsync())
Console.WriteLine(slot.Slot);SolSharp.Wallet:
Keypair— generate a key, or load one withParse(auto-detecting a base58 export, asolana-keygenJSON array, hex, or base64); signs messages and zeroes its secret on dispose.ISigner— the signing abstraction the transaction builder depends on, so the key stays swappable.PublicKey.Verify(message, signature)— Ed25519 verification, kept in Wallet so Core stays crypto-free.
using SolSharp.Wallet;
using var keypair = Keypair.Generate(); // or Keypair.Parse(phantomExport / id.json)
byte[] signature = keypair.Sign(message);
bool ok = keypair.PublicKey.Verify(message, signature);SolSharp.Programs:
- Instruction builders:
SystemProgram(transfer, create / allocate / assign, create-with-seed, durable nonce),ComputeBudgetProgram(compute-unit limit and priority fee),TokenProgram(transfer / transfer-checked, mint / burn, approve / revoke, freeze / thaw, initialize mint / account, close, sync-native — each with atokenProgramoverride for Token-2022),AssociatedTokenAccount,AddressLookupTableProgram(create / extend / deactivate / close), andMemoProgram. ProgramDerivedAddress(FindProgramAddress/TryCreateProgramAddress) andPublicKey.IsOnCurve().Message(legacy) andMessageV0(versioned, loading extra accounts from address lookup tables),Transaction, andTransactionBuilder(Build/BuildV0) — compilation, wire serialization (withTransaction.Deserializeto parse one back, andDecompileInstructionsto resolve a parsed message's instructions to program ids and account keys, loading v0 lookup-table accounts), signing, and base64 output. Every encoding is checked byte-for-byte against the Rustsolana-sdk(via solders) andsolana-py.
using SolSharp.Programs;
using SolSharp.Wallet;
using var payer = Keypair.Parse(secret);
var blockhash = (await rpc.GetLatestBlockhashAsync()).Blockhash;
var tx = new TransactionBuilder()
.SetRecentBlockhash(blockhash)
.AddInstruction(ComputeBudgetProgram.SetComputeUnitPrice(50_000))
.AddInstruction(SystemProgram.Transfer(payer.PublicKey, recipient, 1_000_000))
.Build(payer);
var signature = await rpc.SendTransactionAsync(tx.Serialize());- Core primitives —
PublicKey,Base58,ShortVec - RPC enum + JSON converters (
Commitment) - Program / sysvar / mint constants + validation
-
SolSharp.Wallet— Ed25519 keys, signing/verification, key parsing -
SolSharp.Rpc— HTTP reads (getAccountInfo/getMultipleAccounts/getProgramAccounts/getSignaturesForAddress, balances, blockhash, token supply, ...) +sendTransaction/simulateTransaction; multiplexed WebSocket streaming (slots, logs, accounts, programs, signatures, blocks) with auto-reconnect; DI + resilience -
SolSharp.Programs— System / Token (+ Token-2022) / ATA / Compute Budget / Memo instructions, PDA/ATA, transaction builder - Versioned (v0) transactions + address lookup tables (compile / sign / fetch + decode / ALT program)
- Borsh reader + writer, typed SPL account state (
Mint/TokenAccount),Transaction.Deserialize+ instruction decompilation, and typedTransactionError - Live integration test suite (configurable RPC / WS endpoint)
- Published NuGet package (single
SolSharppackage bundling the four assemblies)
- .NET 8 SDK or later.
dotnet build
dotnet test
dotnet format # apply the enforced code styleThe suite includes a SolSharp.IntegrationTests project that exercises the read and streaming paths against a
live cluster. It targets the public mainnet endpoint by default and is overridable via the SOLSHARP_RPC_URL
and SOLSHARP_WS_URL environment variables; no credentials are committed. These tests hit the network, so they
tolerate rate limits by reporting inconclusive rather than failing, and are tagged Integration. For a fast,
offline-only run, exclude them:
dotnet test --filter "TestCategory!=Integration"To point the integration tests at your own node, set the endpoints (the key stays in your shell, never the repo):
SOLSHARP_RPC_URL=https://your-node SOLSHARP_WS_URL=wss://your-node \
dotnet test --filter "TestCategory=Integration"SolSharp/
src/SolSharp.Core/ Encoding/ Primitives/ Converters/ Constants/
src/SolSharp.Rpc/ Protocol/ Models/ Streaming/ + client, options, DI
src/SolSharp.Wallet/ Keypair (+ parsing), ISigner, PublicKey.Verify / IsOnCurve
src/SolSharp.Programs/ instructions, PDA/ATA, Message/Transaction, TransactionBuilder
src/SolSharp/ packaging facade — bundles the four assemblies into the single NuGet package
tests/ NUnit + FluentAssertions, mirroring each project
(+ SolSharp.IntegrationTests: live-cluster read/streaming checks)
.editorconfig modern C# style, enforced on build
Directory.Build.props
CLAUDE.md conventions and decisions for contributors/agents
docs/USAGE.md task-oriented usage guide with runnable examples
Coreis dependency-light and free of I/O and crypto by design — anything that needs the network or Ed25519 lives in a higher layer.- Wire formats and signing are money-critical: they are validated against known-good vectors, not just round-trips.
- Conventions, layering rules, and design decisions are documented in
CLAUDE.md.
SolSharp handles private keys and builds transactions that move funds. It has not been audited — use at your own risk. Never commit secrets or private keys, and never hand a raw private key to a dependency you do not control: build with a third-party library if you must, but sign with your own signer and simulate before sending.
MIT © Yevhen Koval