- Introduced CLAUDE.md to provide guidance for working with the Claude Code repository, including project overview, build commands, testing guidelines, and performance considerations. - Added INDEX.md for a structured overview of the strfry WebSocket implementation analysis, detailing document contents and usage. - Created SKILL.md for the nostr-websocket skill, covering WebSocket protocol fundamentals, connection management, and performance optimization techniques. - Included multiple reference documents for Go, C++, and Rust implementations of WebSocket patterns, enhancing the knowledge base for developers. - Updated deployment and build documentation to reflect new multi-platform capabilities and pure Go build processes. - Bumped version to reflect the addition of extensive documentation and resources for developers working with Nostr relays and WebSocket connections.
12 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project Overview
ORLY is a high-performance Nostr relay written in Go, designed for personal relays, small communities, and business deployments. It emphasizes low latency, custom cryptography optimizations, and embedded database performance.
Key Technologies:
- Language: Go 1.25.3+
- Database: Badger v4 (embedded key-value store)
- Cryptography: Custom p8k library using purego for secp256k1 operations (no CGO)
- Web UI: Svelte frontend embedded in the binary
- WebSocket: gorilla/websocket for Nostr protocol
- Performance: SIMD-accelerated SHA256 and hex encoding
Build Commands
Basic Build
# Build relay binary only
go build -o orly
# Pure Go build (no CGO) - this is the standard approach
CGO_ENABLED=0 go build -o orly
Build with Web UI
# Recommended: Use the provided script
./scripts/update-embedded-web.sh
# Manual build
cd app/web
bun install
bun run build
cd ../../
go build -o orly
Development Mode (Web UI Hot Reload)
# Terminal 1: Start relay with dev proxy
export ORLY_WEB_DISABLE_EMBEDDED=true
export ORLY_WEB_DEV_PROXY_URL=localhost:5000
./orly &
# Terminal 2: Start dev server
cd app/web && bun run dev
Testing
Run All Tests
# Standard test run
./scripts/test.sh
# Or manually with purego setup
CGO_ENABLED=0 go test ./...
# Note: libsecp256k1.so must be available for crypto tests
export LD_LIBRARY_PATH="${LD_LIBRARY_PATH:+$LD_LIBRARY_PATH:}$(pwd)/pkg/crypto/p8k"
Run Specific Package Tests
# Test database package
cd pkg/database && go test -v ./...
# Test protocol package
cd pkg/protocol && go test -v ./...
# Test with specific test function
go test -v -run TestSaveEvent ./pkg/database
Relay Protocol Testing
# Test relay protocol compliance
go run cmd/relay-tester/main.go -url ws://localhost:3334
# List available tests
go run cmd/relay-tester/main.go -list
# Run specific test
go run cmd/relay-tester/main.go -url ws://localhost:3334 -test "Basic Event"
Benchmarking
# Run benchmarks in specific package
go test -bench=. -benchmem ./pkg/database
# Crypto benchmarks
cd pkg/crypto/p8k && make bench
Running the Relay
Basic Run
# Build and run
go build -o orly && ./orly
# With environment variables
export ORLY_LOG_LEVEL=debug
export ORLY_PORT=3334
./orly
Get Relay Identity
# Print relay identity secret and pubkey
./orly identity
Common Configuration
# TLS with Let's Encrypt
export ORLY_TLS_DOMAINS=relay.example.com
# Admin configuration
export ORLY_ADMINS=npub1...
# Follows ACL mode
export ORLY_ACL_MODE=follows
# Enable sprocket event processing
export ORLY_SPROCKET_ENABLED=true
# Enable policy system
export ORLY_POLICY_ENABLED=true
Code Architecture
Repository Structure
Root Entry Point:
main.go- Application entry point with signal handling, profiling setup, and database initializationapp/main.go- Core relay server initialization and lifecycle management
Core Packages:
app/ - HTTP/WebSocket server and handlers
server.go- Main Server struct and HTTP request routinghandle-*.go- Nostr protocol message handlers (EVENT, REQ, COUNT, CLOSE, AUTH, DELETE)handle-websocket.go- WebSocket connection lifecycle and frame handlinglistener.go- Network listener setupsprocket.go- External event processing script managerpublisher.go- Event broadcast to active subscriptionspayment_processor.go- NWC integration for subscription paymentsblossom.go- Blob storage service initializationweb.go- Embedded web UI serving and dev proxyconfig/- Environment variable configuration using go-simpler.org/env
pkg/database/ - Badger-based event storage
database.go- Database initialization with cache tuningsave-event.go- Event storage with index updatesquery-events.go- Main query execution enginequery-for-*.go- Specialized query builders for different filter patternsindexes/- Index key construction for efficient lookupsexport.go/import.go- Event export/import in JSONL formatsubscriptions.go- Active subscription trackingidentity.go- Relay identity key managementmigrations.go- Database schema migration runner
pkg/protocol/ - Nostr protocol implementation
ws/- WebSocket message framing and parsingauth/- NIP-42 authentication challenge/responsepublish/- Event publisher for broadcasting to subscriptionsrelayinfo/- NIP-11 relay information documentdirectory/- Distributed directory service (NIP-XX)nwc/- Nostr Wallet Connect clientblossom/- Blob storage protocol
pkg/encoders/ - Optimized Nostr data encoding/decoding
event/- Event JSON marshaling/unmarshaling with buffer poolingfilter/- Filter parsing and validationbech32encoding/- npub/nsec/note encodinghex/- SIMD-accelerated hex encoding using templexxx/xhextimestamp/,kind/,tag/- Specialized field encoders
pkg/crypto/ - Cryptographic operations
p8k/- Pure Go secp256k1 using purego (no CGO) to dynamically load libsecp256k1.sosecp.go- Dynamic library loading and function bindingschnorr.go- Schnorr signature operations (NIP-01)ecdh.go- ECDH for encrypted DMs (NIP-04, NIP-44)recovery.go- Public key recovery from signatureslibsecp256k1.so- Pre-compiled secp256k1 library
keys/- Key derivation and conversion utilitiessha256/- SIMD-accelerated SHA256 using minio/sha256-simd
pkg/acl/ - Access control systems
acl.go- ACL registry and interfacefollows.go- Follows-based whitelist (admins + their follows can write)managed.go- NIP-86 managed relay with role-based permissionsnone.go- Open relay (no restrictions)
pkg/policy/ - Event filtering and validation policies
- Policy configuration loaded from
~/.config/ORLY/policy.json - Per-kind size limits, age restrictions, custom scripts
- See
docs/POLICY_USAGE_GUIDE.mdfor configuration examples
pkg/sync/ - Distributed synchronization
cluster_manager.go- Active replication between relay peersrelay_group_manager.go- Relay group configuration (NIP-XX)manager.go- Distributed directory consensus
pkg/spider/ - Event syncing from other relays
spider.go- Spider manager for "follows" mode- Fetches events from admin relays for followed pubkeys
pkg/utils/ - Shared utilities
atomic/- Extended atomic operationsinterrupt/- Signal handling and graceful shutdownapputil/- Application-level utilities
Web UI (app/web/):
- Svelte-based admin interface
- Embedded in binary via
go:embed - Features: event browser, sprocket management, user admin, settings
Command-line Tools (cmd/):
relay-tester/- Nostr protocol compliance testingbenchmark/- Multi-relay performance comparisonstresstest/- Load testing toolaggregator/- Event aggregation utilityconvert/- Data format conversionpolicytest/- Policy validation testing
Important Patterns
Pure Go with Purego:
- All builds use
CGO_ENABLED=0 - The p8k crypto library uses
github.com/ebitengine/puregoto dynamically loadlibsecp256k1.soat runtime - This avoids CGO complexity while maintaining C library performance
libsecp256k1.somust be inLD_LIBRARY_PATHor same directory as binary
Database Query Pattern:
- Filters are analyzed in
get-indexes-from-filter.goto determine optimal query strategy - Different query builders (
query-for-kinds.go,query-for-authors.go, etc.) handle specific filter patterns - All queries return event serials (uint64) for efficient joining
- Final events fetched via
fetch-events-by-serials.go
WebSocket Message Flow:
handle-websocket.goaccepts connection and spawns goroutine- Incoming frames parsed by
pkg/protocol/ws/ - Routed to handlers:
handle-event.go,handle-req.go,handle-count.go, etc. - Events stored via
database.SaveEvent() - Active subscriptions notified via
publishers.Publish()
Configuration System:
- Uses
go-simpler.org/envfor struct tags - All config in
app/config/config.gowithORLY_prefix - Supports XDG directories via
github.com/adrg/xdg - Default data directory:
~/.local/share/ORLY
Event Publishing:
pkg/protocol/publish/manages publisher registry- Each WebSocket connection registers its subscriptions
publishers.Publish(event)broadcasts to matching subscribers- Efficient filter matching without re-querying database
Embedded Assets:
- Web UI built to
app/web/dist/ - Embedded via
//go:embeddirective inapp/web.go - Served at root path
/with API at/api/*
Development Workflow
Making Changes to Web UI
- Edit files in
app/web/src/ - For hot reload:
cd app/web && bun run dev(withORLY_WEB_DISABLE_EMBEDDED=true) - For production build:
./scripts/update-embedded-web.sh
Adding New Nostr Protocol Handlers
- Create
app/handle-<message-type>.go - Add case in
app/handle-message.gomessage router - Implement handler following existing patterns
- Add tests in
app/<handler>_test.go
Adding Database Indexes
- Define index in
pkg/database/indexes/ - Add migration in
pkg/database/migrations.go - Update
save-event.goto populate index - Add query builder in
pkg/database/query-for-<index>.go - Update
get-indexes-from-filter.goto use new index
Environment Variables for Development
# Verbose logging
export ORLY_LOG_LEVEL=trace
export ORLY_DB_LOG_LEVEL=debug
# Enable profiling
export ORLY_PPROF=cpu
export ORLY_PPROF_HTTP=true # Serves on :6060
# Health check endpoint
export ORLY_HEALTH_PORT=8080
Profiling
# CPU profiling
export ORLY_PPROF=cpu
./orly
# Profile written on shutdown
# HTTP pprof server
export ORLY_PPROF_HTTP=true
./orly
# Visit http://localhost:6060/debug/pprof/
# Memory profiling
export ORLY_PPROF=memory
export ORLY_PPROF_PATH=/tmp/profiles
Deployment
Automated Deployment
# Deploy with systemd service
./scripts/deploy.sh
This script:
- Installs Go 1.25.0 if needed
- Builds relay with embedded web UI
- Installs to
~/.local/bin/orly - Creates systemd service
- Sets capabilities for port 443 binding
systemd Service Management
# Start/stop/restart
sudo systemctl start orly
sudo systemctl stop orly
sudo systemctl restart orly
# Enable on boot
sudo systemctl enable orly
# View logs
sudo journalctl -u orly -f
Manual Deployment
# Build for production
./scripts/update-embedded-web.sh
# Or build all platforms
./scripts/build-all-platforms.sh
Key Dependencies
github.com/dgraph-io/badger/v4- Embedded databasegithub.com/gorilla/websocket- WebSocket servergithub.com/minio/sha256-simd- SIMD SHA256github.com/templexxx/xhex- SIMD hex encodinggithub.com/ebitengine/purego- CGO-free C library loadinggo-simpler.org/env- Environment variable configurationlol.mleku.dev- Custom logging library
Testing Guidelines
- Test files use
_test.gosuffix - Use
github.com/stretchr/testifyfor assertions - Database tests require temporary database setup (see
pkg/database/testmain_test.go) - WebSocket tests should use
relay-testerpackage - Always clean up resources in tests (database, connections, goroutines)
Performance Considerations
- Database Caching: Tune
ORLY_DB_BLOCK_CACHE_MBandORLY_DB_INDEX_CACHE_MBfor workload - Query Optimization: Add indexes for common filter patterns
- Memory Pooling: Use buffer pools in encoders (see
pkg/encoders/event/) - SIMD Operations: Leverage minio/sha256-simd and templexxx/xhex
- Goroutine Management: Each WebSocket connection runs in its own goroutine
Release Process
- Update version in
pkg/version/versionfile (e.g., v1.2.3) - Create and push tag:
git tag v1.2.3 git push origin v1.2.3 - GitHub Actions workflow builds binaries for multiple platforms
- Release created automatically with binaries and checksums