Meshline.Sdk 1.1.0

dotnet add package Meshline.Sdk --version 1.1.0
                    
NuGet\Install-Package Meshline.Sdk -Version 1.1.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Meshline.Sdk" Version="1.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Meshline.Sdk" Version="1.1.0" />
                    
Directory.Packages.props
<PackageReference Include="Meshline.Sdk" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Meshline.Sdk --version 1.1.0
                    
#r "nuget: Meshline.Sdk, 1.1.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Meshline.Sdk@1.1.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Meshline.Sdk&version=1.1.0
                    
Install as a Cake Addin
#tool nuget:?package=Meshline.Sdk&version=1.1.0
                    
Install as a Cake Tool

Meshline .NET SDK

.NET 10 · Assembly: Meshline.Sdk · Root namespace: Meshline

NuGet package · Release notes · Source

Developer guide · Complete API reference · Compilable examples

Meshline is a decentralized messaging and social protocol built around self-sovereign identity. Its .NET SDK provides the client workflows and protocol primitives for building applications on a Meshline network:

  • Account routes, home-relay migration, device authorization, and profiles.
  • Contacts, encrypted direct messages, channels, and encrypted groups.
  • HTTP and WebSocket relay access, authentication, session renewal, and notification recovery.
  • SQLite persistence, pending-operation recovery, local conversations, and snapshot queries.
  • Protocol models, Canonical JSON, signature verification, and message encryption.

Use MeshlineClient to coordinate the client components, or compose the components independently. Construction performs no I/O. Applications supply account signing, relay registry access, and platform-specific secret protection.

The optional RpcRelayRegistry and Nep6AccountSigner adapters provide RPC registry access and NEP-6 wallet signing. Applications choose and register the adapters explicitly; platform-specific secret protection remains application-provided. See the integration guide.

Requirements and installation

Install the .NET 10 SDK and target net10.0 in the consuming application. Add the NuGet package:

dotnet add package Meshline.Sdk

For source integration, reference the SDK project instead, replacing the paths below with your project and checkout locations:

dotnet add path/to/MyApp.csproj reference path/to/sdk/dotnet/src/Meshline.Sdk/Meshline.Sdk.csproj

SQLite and its EF Core provider are included. The application needs a writable directory for each local database and access to its chosen Meshline network. A named HTTP client registered through Microsoft DI additionally requires Microsoft.Extensions.Http 10.x; see HTTP configuration.

The SDK is validated by the offline test suite. Live-relay interoperability and cross-platform release validation, including mobile, browser, and NativeAOT environments, remain pending. Relay-server/DHT behavior and application content rendering are outside the SDK test suite.

Application integrations

Implement these interfaces from Meshline.Interactions:

Interface Application responsibility
IAccountSigner Expose the account identifier and matching public key, and sign the supplied bytes using the account's signing mechanism.
IRelayRegistry Supply the NetworkContext, resolve a relay by ID, and enumerate relay entries for that network.
ISecretProtector Protect and unprotect local secrets using the supplied purpose string and the application's platform-specific key protection.

ClientOptions.Context must match the registry's network, and ClientOptions.AccountId must identify the signer's account. A NetworkContext identifies the Neo network reference and registry script hash; obtain these values from the network you intend to use.

An account signer is needed for account establishment, recovery, route publication, and device authorization. A client with a previously authorized local device can be constructed without an account signer for device-authorized operations. Preserve access to the same secret-protection keys when reopening its database.

Quick start

This example establishes a new account and runs an application session. Pass your three integrations, a database path dedicated to this network/account/device, and an asynchronous application loop. The loop receives the running client and returns when the session should end.

using Meshline;
using Meshline.Interactions;
using Meshline.Models.Client;
using Meshline.Storage;
using Meshline.Transport;

public static class MeshlineExample
{
    public static async Task RunNewAccountAsync(
        IRelayRegistry registry,
        IAccountSigner accountSigner,
        ISecretProtector secretProtector,
        string databasePath,
        Func<MeshlineClient, CancellationToken, Task> runApplication,
        CancellationToken cancellationToken = default)
    {
        var options = new ClientOptions
        {
            Context = registry.Context,
            AccountId = accountSigner.AccountId
        };
        var database = new DatabaseOptions { Path = databasePath };
        Directory.CreateDirectory(Path.GetDirectoryName(database.Path)!);
        await MeshlineDatabase.MigrateAsync(database, cancellationToken);

        await using var pool = new RelayClientPool(options, registry);
        await using var client = new MeshlineClient(
            options, database, pool, secretProtector, accountSigner);

        client.BackgroundError += (_, error) =>
            Console.Error.WriteLine($"{error.Operation}: {error.Error}");

        await client.InitializeAsync(cancellationToken);
        await client.EstablishAccountAsync(cancellationToken: cancellationToken);
        await client.StartAsync(cancellationToken);
        try
        {
            await runApplication(client, cancellationToken);
        }
        finally
        {
            await client.StopAsync();
        }
    }
}

EstablishAccountAsync selects an eligible relay through the registry. Pass AccountEstablishmentOptions.RelayId to choose one explicitly. It creates and authorizes the local device and publishes the account route; interrupted establishment can resume against the same database and relay. The client is disposed before the application-owned pool because await using declarations are released in reverse order.

For an existing account on the same device, use its existing database and secret protector, initialize the client, and call StartAsync without EstablishAccountAsync. Startup requires a valid, published authorization for the local device. Initialization does not implicitly create or migrate the database.

Background synchronization reads data over HTTP. WebSocket notifications trigger earlier reads, while periodic HTTP polling continues if the notification connection or a subscription is unavailable. Connection failures are reported through BackgroundError; the SDK reconnects and restores channel and group subscriptions in the background. Each newly confirmed or restored subscription set triggers an HTTP catch-up read; an unchanged set on the same connection does not trigger repeated subscription requests. Starting the client does not wait for WebSocket readiness.

For explicit account recovery, initialize the client with an account signer and call RecoverAccountAsync before starting it. AccountRecoveryOptions can specify a relay, previous device state, and revision values when needed. Recovery publishes device state and an account route; choose it deliberately when restoring access rather than as a routine startup fallback. If resuming with a previously authorized device, keep its database and protected secrets together.

Common operations

The following snippets run inside your application session with a running client and a cancellationToken. Subscribe to events before starting the client if the application needs notifications from the start of synchronization. Events may arrive from background work; UI applications should dispatch updates to their UI thread.

Contacts and direct messages

MessageManager owns contact invitations, requests, acceptance, aliases, removal, and synchronization. Use AddContactAsync to send a request and AcceptContactRequestAsync to accept an incoming request. Establish the required contact authorization before sending direct messages to another account.

Subscribe to incoming messages and send-state changes:

client.MessageManager.MessageReceived += (_, args) =>
{
    foreach (var message in args.Messages)
        Console.WriteLine($"{message.Key.Sender}: {message.Body?.Text}");
};
client.MessageManager.SendStatusChanged += (_, args) =>
    Console.WriteLine($"{args.Status.MessageId}: {args.Status.State}");

With recipientAccountId set to an authorized contact's account identifier, queue a text message:

using Meshline.Models.Client;
using Meshline.Models.Protocol;

var status = await client.MessageManager.SendMessageAsync(
    recipientAccountId,
    new DirectMessageDraft
    {
        Body = new MessageBody { ContentType = "text/plain", Text = "Hello from Meshline!" }
    },
    cancellationToken);
Console.WriteLine($"{status.MessageId}: {status.State}");

The returned MessageSendStatus describes the local outbox operation, not confirmed recipient delivery. The running component submits queued messages and retries pending operations. Observe SendStatusChanged, query GetSendStatusAsync, or read GetOutboxAsync to track progress. GetMessageHistoryAsync reads stored direct messages; CancelMessageAsync attempts to cancel an outbox operation.

Contact changes and received messages commit with the relay synchronization position. Contact record validation permits update times at most five minutes ahead of the local clock. An account contact synchronization batch exceeding this tolerance is rejected and reported through BackgroundError, while subsequent messages continue to be processed. A message without the required local contact authorization is discarded; later contact synchronization does not replay it. Network, storage, and cancellation failures leave an unfinished entry available for retry. Verified account messages have one local sequence, preserve each relay's order, and are deduplicated across relays. GroupManager consumes this stored stream with its own durable cursor and atomically commits group changes with that cursor.

Conversations and local queries

MeshlineClient combines direct messages, followed channels, and groups into local conversations. Query them in batches:

await using var reader = await client.GetConversationsAsync(
    cancellationToken: cancellationToken);
while (true)
{
    var conversations = await reader.ReadNextAsync(50, cancellationToken);
    if (conversations.Count == 0)
        break;
    foreach (var conversation in conversations)
        Console.WriteLine($"{conversation.ConversationId}: {conversation.UnreadCount} unread");
}

ConversationChanged reports updates; MarkReadAsync updates a conversation's read position. Local list queries return QueryReader<T> and accept explicit filters. Each reader holds a fixed SQLite snapshot, so later changes do not reorder its batches. An empty batch marks the end. Open a new reader to refresh and dispose readers promptly to release their transactions. Relay-backed history and administration queries use their protocol paging cursors instead.

Components

MeshlineClient exposes these components:

Component Responsibilities
AccountManager Resolve and publish account routes.
DeviceManager Create, renew, authorize, and remove devices; implement IDeviceSigner.
ProfileManager Resolve and update account profiles; client.Profile exposes the current profile.
MessageManager Contacts, direct messages, outbox state, and account-message synchronization.
ChannelManager Channel creation, publishing, editing, deletion, following, history, and closure.
GroupManager Encrypted group messages, membership, roles, invitations, key rotation/recovery, and closure.

For independently assembled components, follow their constructor dependencies. For example, ProfileManager uses an AccountManager and an IDeviceSigner, which can be the same component group's DeviceManager. Pass that group one shared RelayClientPool.

Lifecycle and ownership

  • Call MeshlineDatabase.MigrateAsync explicitly before initializing components. Constructors only configure objects; InitializeAsync prepares component state, and StartAsync begins background work after account/device authorization.
  • MeshlineClient initializes and starts its components in dependency order, then stops and disposes them in reverse order. Applications composing components independently must follow the same ordering.
  • StopAsync ends background work and subscriptions while retaining shared relay clients in the pool. Each component allows up to five seconds in total for pending subscription requests and the final clear. If the outcome of a sent subscription request is unknown, or the final clear fails, the SDK closes that WebSocket connection; components still running reconnect, restore their subscriptions, and catch up over HTTP. The same connection reset applies to subscription timeouts during normal operation. Components can be started again. DisposeAsync drains active operations and releases their resources.
  • The application owns the pool. Stop and dispose all clients/components using it before disposing it. Supplied signers, registries, and secret protectors remain application-managed integrations.
  • Observe StateChanged for lifecycle changes and BackgroundError for background failures. Awaited operation failures propagate to the caller.

Local storage and migrations

SQLite is the built-in storage engine; other database providers are not supported. Pass DatabaseOptions to clients and components. The SDK configures EF Core internally, so no provider registration or DbContextOptions is needed. DatabaseOptions.Path resolves a relative path against the current working directory when assigned, without opening the database.

Use a separate database for each network, account, and device. The application chooses and creates the parent directory and calls MeshlineDatabase.MigrateAsync to create or upgrade the database. Startup never migrates or deletes it automatically. ISecretProtector protects stored device and group secrets; it does not encrypt the entire SQLite database.

The existing InitialCreate migration is the 1.0.0 database baseline. Pre-1.0 development databases built from a different initial migration are not covered by this upgrade baseline. Preserve the database and the matching secret-protection keys when moving or restoring an application's local state.

Relay connections

Create one RelayClientPool per account and device. ClientOptions supplies the network and account; the device is bound when first used for authentication. Components share the pool's relay clients without returning or disposing them.

Relay clients begin without a session. The pool reuses or authenticates a suitable client when requested, keeping account and device sessions separate. Public requests can use a client in any mode. Device-session WebSocket calls start notification dispatch. Each opened stream has one dispatcher that raises NotificationReceived and continues receiving even if no component handlers remain. Disconnects trigger reconnection without replaying business requests.

RelayClientPool.Clients exposes the current collection. Observe PoolChanged for collection changes and RelayChanged for updates to a client's RelayId, SessionMode, ConnectionState, AuthenticationState, or LastError. SessionMode stays null until authentication succeeds and remains bound after expiry. HTTP connection state reflects observed communication, not a continuously monitored socket; reading authentication state also reflects session expiry. Use GetDescriptorAsync, GetInfoAsync, SendHttpAsync, and SendWebSocketAsync for lower-level access.

HTTP configuration

Without a supplied HttpClient, each pool creates and owns its own client. To configure proxies, logging handlers, or an offline peer, pass a client as the optional third constructor argument. Here, options and registry are the same account/network integrations used above:

using Meshline.Transport;

using var http = new HttpClient(new SocketsHttpHandler
{
    AllowAutoRedirect = false,
    UseCookies = false,
    PooledConnectionLifetime = TimeSpan.FromMinutes(5)
}) { Timeout = Timeout.InfiniteTimeSpan };

await using var pool = new RelayClientPool(options, registry, http);
// Construct and run the client here; dispose it before leaving this scope.

The same HTTP client sends requests and establishes WebSocket connections. The SDK owns each ClientWebSocket. A supplied HTTP client remains caller-owned and must outlive the pool; a pool-owned client is disposed after its relay sessions and pending authentication tasks exit.

The SDK does not change a supplied client's configuration. Disable redirects, cookies, and implicit request retries in its handler pipeline. An infinite HTTP timeout leaves deadlines to SDK cancellation tokens; a finite timeout can end HTTP requests and WebSocket handshakes sooner. Long-lived WebSocket traffic uses session cancellation and request deadlines.

Dependency injection

For Microsoft DI, reference Microsoft.Extensions.Http 10.x in the consuming application. Register a named client using RelayClientPool.HttpClientName ("RelayClientPool") and enable its keyed registration with AddAsKeyed:

using Meshline.Interactions;
using Meshline.Transport;
using Microsoft.Extensions.DependencyInjection;

var services = new ServiceCollection();
services.AddSingleton(options);
services.AddSingleton<IRelayRegistry>(registry);
services.AddHttpClient(RelayClientPool.HttpClientName,
        http => http.Timeout = Timeout.InfiniteTimeSpan)
    .ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler
    {
        AllowAutoRedirect = false,
        UseCookies = false,
        PooledConnectionLifetime = TimeSpan.FromMinutes(5)
    })
    .SetHandlerLifetime(Timeout.InfiniteTimeSpan)
    .AddAsKeyed(ServiceLifetime.Singleton);
services.AddScoped<RelayClientPool>();

await using var provider = services.BuildServiceProvider();
await using var scope = provider.CreateAsyncScope();
var pool = scope.ServiceProvider.GetRequiredService<RelayClientPool>();
// Run the client here and dispose it before the scope ends.

This configuration describes one account/network. Each scope owns a pool for an account/device session while the named HTTP client is shared. Multi-account applications must provide the correct ClientOptions and registry in each account scope. A scoped keyed HTTP client also works when it shares the pool's scope. Dispose scopes asynchronously so relay sessions drain before DI releases the HTTP client. Connection lifetime refreshes connections without rotating handlers beneath the long-lived client.

AddHttpClient(name) alone configures the factory but does not enable keyed injection. If no matching keyed client is registered, the optional constructor argument is null and the pool creates its own client. Unkeyed clients or other names are not selected. Errors resolving a registered keyed client propagate to the caller.

Use the source project

To use a local checkout, run from this directory (dotnet/):

dotnet restore Meshline.Sdk.slnx
dotnet build Meshline.Sdk.slnx -c Release --no-restore

Then add the project reference to your application. The NuGet package is the usual installation path when you do not need to modify the SDK.

The package includes XML API documentation for IntelliSense, covering parameters, return values, lifecycle, ownership, validation, and paging constraints. The developer guide expands the workflows into chapters, and the API reference presents the public surface as generated Markdown. Both follow the source checkout; consult the matching release tag when integrating a different package version.

Validation scope

The offline suite covers protocol and cryptographic vectors, transport, SQLite persistence, account and device workflows, contacts, messaging, channels, groups, lifecycle behavior, RPC registry queries, and NEP-6 wallet signing. These tests use in-memory relay peers, simulated RPC responses, public test wallets, and isolated local databases.

This does not establish live-relay interoperability or support for mobile, browser, or NativeAOT environments. Applications remain responsible for account signing, network registry access, secret protection, content retrieval, and rendering.

The test guide describes the coverage and protocol-vector provenance. For SDK contributions, local checks, and package releases, see the maintenance guide.

Source layout

The source project contains the client, components, interactions, protocol/client models, identity, serialization, validation, storage, and transport in one assembly. The test project contains the offline suite, organized by Protocol, Transport, Storage and domain-specific Components, with reusable Support helpers and an EF Core design-time factory. Public types and method signatures are available in the source alongside the examples above.

Source and license

Product 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.1.0 34 10/3/2026
1.0.0 84 9/27/2026