Skip to content

Base Nostr Transport

The BaseNostrTransport is an abstract class that provides the core functionality for all Nostr-based transports in the @contextvm/sdk. It serves as the foundation for the NostrClientTransport and NostrServerTransport, handling the common logic for connecting to relays, managing subscriptions, and converting messages between the MCP and Nostr formats.

The BaseNostrTransport is responsible for:

  • Connection Management: Establishing and terminating connections to the Nostr relay network via a RelayHandler.
  • Event Serialization: Converting MCP JSON-RPC messages into Nostr events and vice-versa.
  • Cryptographic Operations: Signing Nostr events using a NostrSigner.
  • Message Publishing: Publishing events to the Nostr network.
  • Subscription Management: Creating and managing subscriptions to listen for relevant events.
  • Encryption Handling: Managing the encryption and decryption of messages based on the configured EncryptionMode.

When creating a transport that extends BaseNostrTransport, you must provide a configuration object that implements the BaseNostrTransportOptions interface:

export interface BaseNostrTransportOptions {
signer: NostrSigner;
relayHandler: RelayHandler | string[];
encryptionMode?: EncryptionMode;
giftWrapMode?: GiftWrapMode;
logLevel?: LogLevel;
}
  • signer: An instance of a NostrSigner for signing events. This is a required parameter.
  • relayHandler: An instance of a RelayHandler for managing relay connections, or an array of relay URLs to create an ApplesauceRelayPool automatically. This is a required parameter.
  • encryptionMode: An optional EncryptionMode enum that determines the encryption policy for the transport. Defaults to OPTIONAL.
  • giftWrapMode: An optional GiftWrapMode enum that determines which gift wrap kind to use for encrypted messages. Defaults to OPTIONAL.
  • logLevel: (Optional) Log level for debugging output.

Note: This interface describes the shared base abstraction. A concrete transport may expose a more ergonomic public options type while still normalizing to these base requirements internally. For example, the client transport can resolve relays through discovery flows and therefore allows omitting relayHandler at its public API layer.

The GiftWrapMode enum controls which NIP-59 gift wrap kind is used for encrypted communications:

  • OPTIONAL: Negotiates with the remote peer and uses ephemeral gift wraps (kind 21059) when both sides support it, falling back to persistent gift wraps (kind 1059) otherwise. This is the default.
  • EPHEMERAL: Always uses ephemeral gift wraps (kind 21059). Only use this when you know the remote peer supports ephemeral gift wraps.
  • PERSISTENT: Always uses persistent gift wraps (kind 1059). Use this for maximum compatibility with existing implementations.

Ephemeral gift wraps (kind 21059) are preferred for privacy-sensitive communications as relays are not expected to store them. See CEP-19: Ephemeral Gift Wraps for more details.

import { NostrClientTransport, GiftWrapMode } from "@contextvm/sdk";
const transport = new NostrClientTransport({
signer,
relayHandler: ["wss://relay.damus.io"],
giftWrapMode: GiftWrapMode.EPHEMERAL,
});

The relayHandler option provides flexibility in how you configure relay connections:

For advanced use cases, create and configure your own relay handler:

import { ApplesauceRelayPool } from "@contextvm/sdk";
const relayHandler = new ApplesauceRelayPool([
"wss://relay.damus.io",
"wss://relay.primal.net",
]);
const transport = new NostrClientTransport({
signer,
relayHandler,
});

For simple use cases, pass an array of relay URLs and the transport will create an ApplesauceRelayPool automatically:

const transport = new NostrClientTransport({
signer,
relayHandler: ["wss://relay.damus.io", "wss://relay.primal.net"],
});

This is the recommended approach for most use cases as it provides sensible defaults for relay management.

The BaseNostrTransport provides several protected methods that are used by its subclasses to implement their specific logic:

  • connect(): Connects to the configured Nostr relays.
  • disconnect(): Disconnects from the relays and clears subscriptions.
  • subscribe(filters, onEvent): Subscribes to Nostr events that match the given filters.
  • sendMcpMessage(...): Converts an MCP message to a Nostr event, signs it, optionally encrypts it, and publishes it to the network.
  • createSubscriptionFilters(...): A helper method to create standard filters for listening to messages directed at a specific public key.

The BaseNostrTransport encapsulates the shared logic of Nostr communication, allowing the NostrClientTransport and NostrServerTransport to focus on their specific roles in the client-server interaction model. By building on this common base, the SDK ensures consistent behavior and a unified approach to handling MCP over Nostr.

Now that you understand the foundation of the Nostr transports, let’s explore the concrete implementations: