go-nostr

repository·master·Indexed 19 days ago

https://github.com/nbd-wtf/go-nostr

A Go implementation of NIP-44 providing tools for secure encrypted messaging on the Nostr protocol. It includes functionality for generating Nostr keys, NIP-19 encodings (nsec/npub), connecting to relays via WebSockets, subscribing to and publishing events, and handling various Nostr envelopes (EVENT, REQ, OK, etc.). The library supports high-performance signing via libsecp256k1 and is currently in maintenance mode.

Tokens
17.1K
Snippets
87
Records
103
Agent score
64%

What's inside go-nostr

  1. Avoid goroutine bloat in subscriptions

    master

    When using relay.Subscribe, you must manage the lifecycle of the subscription to prevent goroutine leaks. If you stop listening to the sub.Events channel without properly closing the subscription, the library will continue to spawn goroutines for every new event that arrives.

    To prevent this:

    1. Ensure the context.Context passed to Subscribe is eventually canceled.
    2. Or, explicitly call .Unsub() on the subscription object.
  2. Enable libsecp256k1 for high-performance signing

    master

    For significantly faster signing and verification, you can use the libsecp256k1 shared library. This requires the host to have libsecp256k1 installed and CGO support enabled.

    To use it, include the tag during compilation:

    go build -tags=libsecp256k1 .
  3. Install go-nostr

    master

    To add go-nostr to your Go project, use the following command:

    go get github.com/nbd-wtf/go-nostr

    Note: This repository is in maintenance mode. For new projects, the maintainers recommend using fiatjaf.com/nostr@master instead.

  4. Configure logging and debug output

    master

    Enable Debug Logs

    To see detailed interaction logs with relays in STDOUT, compile or run your program with the -tags debug flag.

    Disable Info Logs

    To suppress all info logs, replace the global nostr.InfoLogger with a logger that discards output:

    nostr.InfoLogger = log.New(io.Discard, "", 0)
  5. Use MultiStore to interact with multiple relays simultaneously

    master

    A MultiStore is a collection of RelayStore implementations (a slice of RelayStore). It allows you to treat multiple relays as a single unified store. When you call methods on a MultiStore, it broadcasts the operation to all underlying stores and aggregates the results or errors.

    • Publish: Sends an event to all relays in the store. Returns a joined error if any individual publish fails.
    • QueryEvents: Starts a streaming query across all relays. It spawns goroutines to pipe events from each individual relay channel into a single unified channel. Returns an error only if all underlying stores fail to provide a channel.
    • QuerySync: Performs a synchronous fetch from all relays. It aggregates all returned events into a single slice and sorts them by CreatedAt in descending order (newest first).
    type MultiStore []RelayStore
  6. Use the Envelope interface

    master

    The Envelope interface is the common type for all Nostr message types. Any object implementing this interface can be used to handle incoming or outgoing relay messages. It provides methods for identifying the message type, parsing from JSON, and serializing back to JSON.

    type Envelope interface {
    	Label() string
    	FromJSON(string) error
    	MarshalJSON() ([]byte, error)
    	String() string
    }
  7. Use the Pointer interface for Nostr references

    master

    The Pointer interface provides a unified way to handle references to Nostr entities (profiles, events, or addressable entities). It allows you to convert a reference into a Tag for inclusion in new events, a Filter for querying relays, or a simple string reference.

    Supported implementations include:

    • ProfilePointer: References a user profile (typically via p tags).
    • EventPointer: References a specific event (typically via e tags).
    • EntityPointer: References an addressable entity (typically via a tags).
  8. How SimplePool manages relay connections

    master

    A SimplePool acts as a high-level orchestrator for Nostr relays. Its core responsibilities include:

    1. Connection Management: It uses EnsureRelay(url) to check if a connection to a specific relay exists and is active. If not, it attempts to connect with a 15-second timeout.
    2. Automatic Reconnection: For long-lived subscriptions (SubscribeMany), the pool automatically attempts to reconnect to relays if they disconnect or if the subscription dies, using an exponential backoff strategy.
    3. Authentication Flow: If a relay requires authentication (indicated by a CLOSED message with an auth-required: prefix), the pool uses the configured authHandler to perform the handshake and then automatically resumes the subscription or publication.
    4. Deduplication: The pool tracks seen event IDs to prevent processing the same event multiple times across different relays, optionally triggering a WithDuplicateMiddleware callback.
  9. Manage relay subscriptions with the Subscription type

    master

    The Subscription type represents an active subscription to a Nostr relay. It provides channels to receive incoming events, handle end-of-stored-events (EOSE), and monitor the reason for closure.

    Key Channels

    • Events (*chan *Event): Emits all *Event objects received from the relay. This channel is closed when the subscription ends.
    • EndOfStoredEvents (chan struct{}): This channel is closed when an EOSE message is received, signaling that the relay has finished sending all stored events matching the filters.
    • ClosedReason (chan string): Emits the reason string when a CLOSED message is received from the relay.
    • Context (context.Context): The subscription's lifecycle is tied to this context. When the context is canceled, the subscription ends.

    Lifecycle Management

    • Use Unsub() to gracefully close the subscription. This sends a CLOSE message to the relay (per NIP-01) and closes the Events channel.
    • Use Close() if you only want to send a CLOSE message to the relay without necessarily triggering the full unsubscription logic.
    • The subscription is automatically terminated if the provided context.Context expires or is canceled.
    // Example conceptual usage
    sub := relay.Subscribe(ctx, filters, nostr.WithLabel("my-sub"))
    
    go func() {
        for evt := range sub.Events {
            fmt.Printf("Received event: %s\n", evt.ID)
        }
    }()
    
    go func() {
        <-sub.EndOfStoredEvents
        fmt.Println("All stored events received.")
    }()
    
    go func() {
        reason := <-sub.ClosedReason
        fmt.Printf("Subscription closed: %s\n", reason)
    }()
  10. Melt Lightning Network (Bolt11) via Quotes

    master

    To melt (exchange) Cashu tokens for a Lightning Network (Bolt11) payment, follow the quote-then-melt pattern:

    1. Request a Melt Quote: Use PostMeltQuoteBolt11 with a nut05.PostMeltQuoteBolt11Request.
    2. Check Status: Use GetMeltQuoteState with the quoteId to poll for the status.
    3. Complete Melting: Use PostMeltBolt11 with a nut05.PostMeltBolt11Request to finalize the transaction.
    // 1. Request melt quote
    meltQuoteReq := nut05.PostMeltQuoteBolt11Request{...}
    meltQuoteResp, err := client.PostMeltQuoteBolt11(ctx, mintURL, meltQuoteReq)
    
    // 2. Check state
    state, err := client.GetMeltQuoteState(ctx, mintURL, meltQuoteResp.QuoteID)
    
    // 3. Post melt
    meltReq := nut05.PostMeltBolt11Request{...}
    meltResp, err := client.PostMeltBolt11(ctx, mintURL, meltReq)
  11. Initialize a SimplePool

    master

    Use NewSimplePool to create a manager for multiple relay connections. The pool handles connection lifecycle, reconnection logic, and prevents duplicate connections to the same relay. You can pass several PoolOptions to configure behavior like authentication, middleware, or a penalty box for failing relays.

    import "github.com/nbd-wtf/go-nostr"
    
    // Create a new pool with a context
    pool := nostr.NewSimplePool(ctx, 
        nostr.WithRelayOptions(nostr.WithRequestHeader(myHeaders)),
        nostr.WithAuthHandler(myAuthFunc),
        nostr.WithPenaltyBox(),
    )