Stellar API SDK for .NET
Report Bug ·
Request Feature ·
Report Security Vulnerability
dotnet-stellar-sdk is a .NET library for communicating with
a Stellar Horizon server
or Stellar RPC server.
It is used for building Stellar apps.
This project originated as a full port of the official Java SDK API.
The SEP (Stellar Ecosystem Proposal) protocol implementations were ported from the Flutter SDK.
- Built-in HTTP resilience for Stellar - Opt-in retries for 408/429/5xx tuned for Horizon (
ForHorizon()) and Stellar RPC / Soroban (ForSoroban()), withRetry-Afterhonored (capped byMaxRetryAfterDelay, default 1 minute) - Configurable - Customize retry count, delays, jitter, status codes, and method filter
- Full Stellar Support - Works with both Horizon API and Stellar RPC servers
The stellar-dotnet-sdk library is bundled in a NuGet package.
- Using the console
- Run
Install-Package stellar-dotnet-sdkin the console.
- Run
- Using
the NuGet Package Manager
- Search this package NuGet Package and install it.
The stellar-dotnet-sdk and stellar-dotnet-sdk-xdr packages multi-target the following frameworks:
| Target framework | Typical platforms | Crypto backend |
|---|---|---|
net10.0 |
.NET 10 apps | NSec |
net8.0 |
.NET 8 apps | NSec |
netstandard2.1 |
Unity 6, Unity 2022.3 (see compiler note below), Tizen 5.5+, portable libraries | Sodium.Core |
NuGet resolves the best matching assembly for your project automatically.
.NET MAUI: validated on an Android emulator and four physical Android devices; iOS is not validated yet. See MAUI compatibility for results, required workarounds (including for the published 15.1.0 package on Android), trimming settings and known limitations.
- SEP-0009 date fields (
BirthDate,IdIssueDate,IdExpirationDate,RegistrationDate):DateOnly?onnet8.0/net10.0;string?(ISOyyyy-MM-dd) onnetstandard2.1. JSON wire format is identical across TFMs. - Synchronous
HttpClient.Sendresilience: onnet8.0/net10.0,RetryingHttpMessageHandleroverrides syncSendwith the full retry pipeline. On thenetstandard2.1assembly (also whatnet5–net7apps resolve), syncSendthrowsNotSupportedExceptioninstead of silently bypassing retries — useSendAsync. - Unity compiler note (
requiredmembers): many SDK request/response models declare C#requiredmembers. Constructing them from your code needs a compiler that understandsrequired: Unity 6 qualifies; Unity 2022.3's bundled compiler (Roslyn 4.1) does not — member initialization of those types fails to compile there unless you upgrade the compiler (e.g. UnityRoslynUpdater). Deserialization and APIs withoutrequiredmembers are unaffected.
The SDK includes numerous example applications showcasing its features. Explore these standalone projects:
The SDK ships with opt-in retry support tuned for Stellar's HTTP surfaces (Horizon and Stellar RPC). Pick the preset that matches the server you're calling:
// For Horizon — GET queries and SubmitTransaction()
var resilience = HttpResilienceOptionsPresets.ForHorizon();
var server = new Server("https://horizon-testnet.stellar.org", resilience, bearerToken: null);
// For Stellar RPC (Soroban)
var rpcResilience = HttpResilienceOptionsPresets.ForSoroban();
var rpcClient = new DefaultStellarSdkHttpClient(resilienceOptions: rpcResilience);
var rpcServer = new StellarRpcServer("https://soroban-testnet.stellar.org", rpcClient);Both retry HTTP 408, 429, 500, 502, 503, 504 on the Stellar requests they're scoped to (see the
matrix below) with exponential backoff, jitter, and Retry-After honored (capped by
MaxRetryAfterDelay, default 1 minute). They differ in retry budget and delay range.
HttpResilienceOptions.RetryHttpMethods is an explicit whitelist. By default it contains only the
RFC-safe methods (GET, HEAD, OPTIONS). Each preset opts in to the additional methods it needs.
| Stellar operation | HTTP method | Retried by ForHorizon() |
Retried by ForSoroban() |
|---|---|---|---|
Horizon queries (Server.Accounts, Ledgers, Operations, Effects, Root, …) |
GET | ✅ | ✅ |
Server.SubmitTransaction() / SubmitTransactionAsync() |
POST | ✅ | n/a |
Every StellarRpcServer method — read (getLatestLedger, simulateTransaction, getEvents, …) or write (sendTransaction) |
POST | n/a | ✅ |
SEP-6 PATCH /transactions/{id} |
PATCH | ❌ | ❌ |
SEP-38 POST /quote (QuoteService.PostQuoteAsync) |
POST |
Retrying SubmitTransaction() is safe on Stellar even though it is HTTP POST: every envelope is
uniquely keyed by transaction hash plus the source account's sequence number, so a resubmit either
returns the cached server result or fails with tx_bad_seq — there is no double-spend window. If
the original submission had already committed when the transient failure occurred, the retry
surfaces tx_bad_seq and your code should look up the transaction by its hash to recover the
original result.
⚠️ Do not wireForHorizon()orForSoroban()into SEP service clients (ClientWebAuth,InteractiveService,TransferServerService,KycService,UriScheme,QuoteService,StellarTomlwith a customHttpClient). Specific SEP POST endpoints are non-idempotent by spec and silently retrying them creates real problems:
- SEP-10
POST /auth— the spec says: "The Server should not provide more than one JWT for a specific challenge transaction." The challenge is one-shot. On transient failure, request a fresh challenge — don't resubmit the same body.- SEP-24
POST /transactions/{deposit,withdraw}/interactive— each call mints a freshtransaction_id. Retrying creates a duplicate transaction record and an orphaned interactive URL. The spec defines no idempotency-key mechanism.- SEP-6
PATCH /transactions/{id}— not in the SEP-6 master spec; anchor-vendor extension that mutates KYC state. Treat as non-idempotent.- SEP-7 callback
POST(UriScheme.SubmitToCallbackAsync,SignAndSubmitTransactionAsync) — the spec says nothing about idempotency; the POST delivers a signed transaction to a URL the requester chose, and a retry delivers it again. UseNoRetry()(the default) forUriScheme: even transport retries replay it.- SEP-12
POST /customer/files— each call stores the upload under a freshfile_id. Retrying after the anchor already stored it leaves an orphaned duplicate file.- SEP-38
POST /quote— each call creates a new firm quote that the anchor holds in reserve until it expires. Both presets retryPOSTon a 408, 429, 500, 502, 503 or 504 answer, so one call can reserve two quotes.For the other SEP HttpClients, use
WithConnectionRetries()(transport retries only) or build a customHttpResilienceOptionswhoseRetryHttpMethodscontains onlyGET/HEAD/OPTIONS. Note that transport (connection-failure) retries apply to all HTTP methods: a POST whose response was lost may already have been processed server-side, so evenWithConnectionRetries()carries a small replay window. If that window is unacceptable, useNoRetry()and recover at the application level (e.g. request a fresh SEP-10 challenge).
ForHorizon()— for the Stellar Horizon API. Retries the transient HTTP status codes on Horizon's GET queries andSubmitTransaction()POST. 3 retries, 200ms–5s exponential backoff with jitter, honorsRetry-After. PATCH/PUT/DELETE are not retried.ForSoroban()— for Stellar RPC (Soroban). Same status-code set asForHorizon(); adds POST to the retry-method whitelist (every JSON-RPC call is POST). Higher retry budget (5) and longer delays (up to 15s) — tuned for long-running polling workflows likegetTransaction(hash). For latency-sensitive one-off calls (e.g.simulateTransaction), overrideMaxRetryCount/MaxDelay.WithConnectionRetries()— transport-level retries only (HttpRequestExceptionand timeouts thrown inside the handler chain; anHttpClient.Timeoutcancellation is never retried). No status-code retries, but transport retries apply to all HTTP methods — see the SEP warning above for the replay window this implies on one-shot endpoints.LowLatency()— minimal retries and short delays (trading bots, latency-sensitive workloads).NoRetry()— no retries at all.
The retry pipeline observes responses inside the HTTP handler chain, so it triggers
before status codes are translated into typed exceptions like TooManyRequestsException
and ServiceUnavailableException. Those exceptions also expose a
RetryAfterDelay (TimeSpan?) accessor for direct inspection when needed.
A note on Retry-After for Stellar services. Horizon sends Retry-After only on
HTTP 429 (always as an integer number of seconds); on HTTP 503 it relies on the client's
configured backoff. Stellar RPC (Soroban) does not send Retry-After at all, so when it
reports overload as HTTP 503/504 the retry pipeline falls back to exponential backoff. The
parser still accepts the RFC 7231 HTTP-date form because upstream proxies and CDNs
(Cloudflare, nginx, API gateways) may rewrite the header.
JSON-RPC errors are not retried. Stellar RPC reports request-scoped failures — an
out-of-range startLedger, malformed parameters, a TTL ledger key queried directly — as a
JSON-RPC error body delivered with HTTP status 200. The retry pipeline keys on the status
code, and 200 is not a failure, so these never enter it: neither RetryHttpStatusCodes nor
AdditionalRetriableExceptionTypes can reach them and no backoff applies. They surface as
SorobanRpcException, carrying the server's own error code and message, and are meant to be
handled at the call site.
Documentation is available here.
For information on how to contribute, please refer to our contribution guide.
dotnet-stellar-sdk is licensed under an Apache-2.0 license. See
the LICENSE file for details.