nostr_access

An Elixir library for querying Nostr relays with caching and deduplication.

Features

Installation

Add nostr_access to your list of dependencies in mix.exs:

def deps do
[
{:nostr_access, "~> 0.1.0"}
]
end

Quick Start

Command Line Interface

The nostr_access package includes a command-line tool that follows the nak tool syntax:

# Build the CLI tool
mix escript.build
# Generate a filter for kind 1 events
./nostr_access --bare -k 1 -l 10
# Query multiple relays
./nostr_access -k 1 -l 15 wss://relay.example.com wss://nostr-pub.wellorder.net
# Use multiple authors and kinds
./nostr_access --bare -a pubkey1 -a pubkey2 -k 1 -k 6
# With tags and time filters
./nostr_access --bare -k 1 -t e=event123 -s 1700000000 -u 1700003600
# Stream events in real-time
./nostr_access --stream -k 1 wss://relay.example.com
# Paginate through events
./nostr_access --paginate --paginate-global-limit 100 -k 1 wss://relay.example.com

Programmatic API

Fetching Events (Synchronous)

# Fetch text notes from a single relay
{:ok, events} = Nostr.Client.fetch(
["wss://relay.example.com"],
%{kinds: [1]}
)
# Fetch events from multiple relays
{:ok, events} = Nostr.Client.fetch(
["wss://relay1.com", "wss://relay2.com"],
%{authors: ["pubkey1", "pubkey2"], kinds: [1, 6]}
)
# With custom options
{:ok, events} = Nostr.Client.fetch(
["wss://relay.example.com"],
%{kinds: [1]},
idle_ms: 1000,
overall_timeout: 60_000,
cache?: false
)

Streaming Events (Asynchronous)

# Start a streaming query
{:ok, query_ref} = Nostr.Client.stream(
["wss://relay1.com", "wss://relay2.com"],
%{kinds: [1]}
)
# Receive events
receive do
{:nostr_event, ^query_ref, event} ->
IO.puts("Received event: #{event["id"]}")
{:nostr_eose, ^query_ref, true} ->
IO.puts("All relays finished")
{:nostr_eose, ^query_ref, false} ->
IO.puts("One relay finished")
end
# Cancel a streaming query
Nostr.Client.cancel(query_ref)

Configuration

Configure nostr_access in your config/config.exs:

config :nostr_access,
idle_ms: 500, # Inactivity window (ms)
overall_timeout: 30_000, # Hard stop timeout (ms)
cache?: true, # Enable/disable caching
dedup_strategy: Nostr.Dedup.Default # Deduplication strategy

CLI Reference

The nostr_access command-line tool follows the nak tool syntax for compatibility:

Usage

nostr_access [options] [relay...]

Options

Filter Attributes

Output Options

Global Options

Examples

# Generate a filter for kind 1 events
./nostr_access --bare -k 1 -l 10
# Query multiple relays
./nostr_access -k 1 -l 15 wss://relay.example.com wss://nostr-pub.wellorder.net
# Use multiple authors and kinds
./nostr_access --bare -a pubkey1 -a pubkey2 -k 1 -k 6
# With tags and time filters
./nostr_access --bare -k 1 -t e=event123 -s 1700000000 -u 1700003600
# Stream events in real-time
./nostr_access --stream -k 1 wss://relay.example.com
# Paginate through events
./nostr_access --paginate --paginate-global-limit 100 -k 1 wss://relay.example.com

API Reference

Nostr.Client.fetch/3

Fetches events from relays synchronously.

@spec fetch([relay_uri], filter, Keyword.t()) :: {:ok, [event]} | {:error, term()}

Options:

Nostr.Client.stream/3

Starts a streaming query to relays.

@spec stream([relay_uri], filter, Keyword.t()) :: {:ok, query_ref} | {:error, term()}

Messages received:

Nostr.Client.cancel/1

Cancels a streaming query.

@spec cancel(query_ref) :: :ok | {:error, :not_found}

Filter Examples

# Text notes from specific authors
%{kinds: [1], authors: ["pubkey1", "pubkey2"]}
# Recent events
%{kinds: [1], since: System.system_time(:second) - 3600}
# Events with specific tags
%{kinds: [1], "#t" => ["nostr", "elixir"]}
# Addressable events
%{kinds: [30000], authors: ["pubkey"], "#d" => ["identifier"]}
# Limited results
%{kinds: [1], limit: 100}

Architecture

The library uses a supervision tree with the following components:

nostr_access.Application (Supervisor, :rest_for_one)
├─ Registry.NostrQueries # {query_ref, pid}
├─ Nostr.Cache.Events (Cachex, TTL 2 h)
├─ Nostr.Cache.Miss (Cachex, TTL 10 min)
├─ Nostr.Telemetry.Supervisor
├─ DynamicSupervisor.QuerySup # one Nostr.Query per request
└─ DynamicSupervisor.RelayPoolSup # one per relay URI
└─ Nostr.RelayPool (GenServer)
└─ 03 Nostr.Connection (WebSockex) # ≤10 subs each

Deduplication Strategy

The library follows NIP-01 rules for event deduplication:

Event ClassUniqueness Key
Addressable (kind 30000-39999){kind, pubkey, d_tag}
Lists & Metadata (kind 0, 10000-19999){kind, pubkey}
All other kindsevent.id

You can implement custom deduplication strategies by implementing the Nostr.Dedup behaviour.

Testing

Run the test suite:

mix test

Run with coverage:

MIX_ENV=test mix test --cover

Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Add tests
  5. Submit a pull request

License

This project is licensed under the MIT License - see the LICENSE file for details.

Acknowledgments