https://github.com/block-core/nostr-client
https://github.com/block-core/nostr-client
Last synced: 3 months ago
JSON representation
- Host: GitHub
- URL: https://github.com/block-core/nostr-client
- Owner: block-core
- License: apache-2.0
- Created: 2025-03-18T19:07:08.000Z (about 1 year ago)
- Default Branch: master
- Last Pushed: 2026-01-27T15:40:14.000Z (5 months ago)
- Last Synced: 2026-01-28T02:16:56.647Z (5 months ago)
- Language: C#
- Homepage: https://block-core.github.io/nostr-client/
- Size: 126 MB
- Stars: 1
- Watchers: 4
- Forks: 0
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- License: LICENSE
- Agents: AGENTS.md
Awesome Lists containing this project
README

# Nostr client
[](https://github.com/Marfusios/nostr-client/actions/workflows/dotnet-core.yml) [](https://badge.fury.io/nu/Nostr.Client) [](https://www.nuget.org/packages/Nostr.Client)
This is a C# implementation of the Nostr protocol found here:
https://github.com/nostr-protocol/nips
Nostr protocol is based on websocket communication.
This library keeps a reliable connection to get real-time data and fast execution of your commands.
[Releases and breaking changes](https://github.com/Marfusios/nostr-client/releases)
### License:
Apache License 2.0
### Features
* installation via NuGet ([Nostr.Client](https://www.nuget.org/packages/Nostr.Client))
* targeting .NET 6.0 and higher (.NET Core, Linux/MacOS compatible)
* reactive extensions ([Rx.NET](https://github.com/Reactive-Extensions/Rx.NET))
### Usage
#### Receiving events
```csharp
var url = new Uri("wss://relay.damus.io");
using var communicator = new NostrWebsocketCommunicator(url);
using var client = new NostrWebsocketClient(communicator, null);
client.Streams.EventStream.Subscribe(response =>
{
var ev = response.Event;
Log.Information("{kind}: {content}", ev?.Kind, ev?.Content)
if(ev is NostrMetadataEvent evm) {
Log.Information("Name: {name}, about: {about}", evm.Metadata?.Name, evm.Metadata?.About);
}
});
await communicator.Start();
```
#### Sending event
```csharp
var ev = new NostrEvent
{
Kind = NostrKind.ShortTextNote,
CreatedAt = DateTime.UtcNow,
Content = "Test message from C# client"
};
var key = NostrPrivateKey.FromBech32("nsec1xxx");
var signed = ev.Sign(key);
client.Send(new NostrEventRequest(signed));
```
#### Sending encrypted direct message (NIP-04)
```csharp
var sender = NostrPrivateKey.FromBech32("nsec1l0a7m5dlg4h9wurhnmgsq5nv9cqyvdwsutk4yf3w4fzzaqw7n80ssdfzkg");
var receiver = NostrPublicKey.FromBech32("npub1dd668dyr9un9nzf9fjjkpdcqmge584c86gceu7j97nsp4lj2pscs0xk075");
var ev = new NostrEvent
{
CreatedAt = DateTime.UtcNow,
Content = $"Test private message from C# client"
};
var encrypted = ev.EncryptDirect(sender, receiver);
var signed = encrypted.Sign(sender);
client.Send(new NostrEventRequest(signed));
```
#### Multi relays support
```csharp
var relays = new[]
{
new NostrWebsocketCommunicator(new Uri("wss://relay.snort.social")),
new NostrWebsocketCommunicator(new Uri("wss://relay.damus.io")),
new NostrWebsocketCommunicator(new Uri("wss://nos.lol"))
};
var client = new NostrMultiWebsocketClient(NullLogger.Instance, relays);
client.Streams.EventStream.Subscribe(HandleEvent);
relays.ToList().ForEach(relay => relay.Start());
```
More usage examples:
* Tests ([link](tests/Nostr.Client.Tests))
* Console sample ([link](test_integration/Nostr.Client.Sample.Console/Program.cs))
* NostrDebug - Blazor app ([link](apps/nostr-debug/NostrDebug.Web), [deployed](https://nostrdebug.com))

### NIP's coverage
- [x] NIP-01: Basic protocol flow description
- [x] NIP-02: Contact List and Petnames (No petname support)
- [ ] NIP-03: OpenTimestamps Attestations for Events
- [x] NIP-04: Encrypted Direct Message
- [ ] NIP-05: Mapping Nostr keys to DNS-based internet identifiers
- [ ] NIP-06: Basic key derivation from mnemonic seed phrase
- [ ] NIP-07: `window.nostr` capability for web browsers
- [ ] NIP-08: Handling Mentions
- [ ] NIP-09: Event Deletion
- [ ] NIP-10: Conventions for clients' use of `e` and `p` tags in text events
- [ ] NIP-11: Relay Information Document
- [ ] NIP-12: Generic Tag Queries
- [ ] NIP-13: Proof of Work
- [ ] NIP-14: Subject tag in text events
- [x] NIP-15: End of Stored Events Notice
- [x] NIP-19: bech32-encoded entities
- [x] NIP-20: Command Results
- [ ] NIP-21: `nostr:` Protocol handler (`web+nostr`)
- [ ] NIP-25: Reactions
- [ ] NIP-26: Delegated Event Signing (Display delegated signings only)
- [ ] NIP-28: Public Chat
- [ ] NIP-36: Sensitive Content
- [ ] NIP-40: Expiration Timestamp
- [ ] NIP-42: Authentication of clients to relays
- [ ] NIP-50: Search
- [ ] NIP-51: Lists
- [ ] NIP-65: Relay List Metadata
**Pull Requests are welcome!**
### Reconnecting
A built-in reconnection invokes after 1 minute (default) of not receiving any messages from the server.
It is possible to configure that timeout via `communicator.ReconnectTimeout`.
Also, a stream `ReconnectionHappened` sends information about a type of reconnection.
However, if you are subscribed to low-rate channels, you will likely encounter that timeout - higher it to a few minutes or implement `ping-pong` interaction on your own every few seconds.
In the case of Nostr relay outage, there is a built-in functionality that slows down reconnection requests
(could be configured via `client.ErrorReconnectTimeout`, the default is 1 minute).
Beware that you **need to resubscribe to channels** after reconnection happens. You should subscribe to `ReconnectionHappened` stream and send subscription requests.
### Testing
The library is prepared for replay testing. The dependency between `Client` and `Communicator` is via abstraction `INostrCommunicator`. There are two communicator implementations:
* `NostrWebsocketCommunicator` - real-time communication with Nostr relay.
* `NostrFileCommunicator` - a simulated communication, raw data are loaded from files and streamed.
Feel free to implement `INostrCommunicator` on your own, for example, load raw data from database, cache, etc.
Usage:
```csharp
var communicator = new NostrFileCommunicator();
communicator.FileNames = new[]
{
"data/nostr-data.txt"
};
communicator.Delimiter = "\n";
var client = new NostrWebsocketClient(communicator);
client.Streams.EventStream.Subscribe(trade =>
{
// do something with an event
});
await communicator.Start();
```
### Multi-threading and other considerations
See [Websocket Client readme](https://github.com/Marfusios/websocket-client#multi-threading)