Skip to content

PlebOne/zaprank

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

⚡ ZapRank - Nostr Zap Leaderboard

Real-time global leaderboard tracking top Lightning zappers on Nostr

Built with Rails 8.1, Hotwire Turbo, PostgreSQL 16 + TimescaleDB, and pure love for Bitcoin and Nostr.

🌐 Live at: zaprank.com


🚀 Features

  • Real-time Leaderboards: All-time, 30d, 7d, and 24h top 100 zappers
  • Live Auto-updates: Leaderboard refreshes every 15 seconds via Turbo Streams (no polling JS!)
  • Beautiful Profile Pages: Detailed stats, charts, and zap history for each zapper
  • TimescaleDB Hypertables: Optimized for 500M+ zap rows with < 80ms query times
  • Permanent Zap Ingestion: Background job connects to 30+ relays and ingests kind 9735 events 24/7
  • Profile Metadata Caching: Automatically fetches and caches kind 0 metadata (name, picture, nip05, lud16)
  • Dark Mode First: Beautiful, mobile-perfect Tailwind UI
  • Zero Redis: Everything uses Solid Queue, Solid Cache, Solid Cable (PostgreSQL-backed)
  • Zero Node.js: Pure Rails 8 with importmaps

📦 Tech Stack

Component Technology
Framework Rails 8.1 (Ruby 3.4.7)
Database PostgreSQL 16 + TimescaleDB extension
Background Jobs Solid Queue (PostgreSQL-backed)
Caching Solid Cache (PostgreSQL-backed)
WebSockets Solid Cable (PostgreSQL-backed)
Frontend Hotwire Turbo + Stimulus
Styling Tailwind CSS 4 (importmapped, zero Node.js)
Charts Chartkick + Chart.js
Nostr Faye::WebSocket for relay connections
Deployment Docker + Caddy 2 (automatic HTTPS)

🏗️ Architecture

Models

Profile (profiles table)

  • Caches zapper metadata: name, picture, nip05, lud16, about
  • Denormalized stats: total_sats_sent, total_zaps_sent, first/last_zap_at
  • Metadata TTL: 24 hours

Zap (zaps TimescaleDB hypertable)

  • Partitioned by paid_at with 7-day chunks
  • Stores: event_id, zapper_pubkey, recipient_pubkey, amount_msat, bolt11, description
  • Continuous aggregate: zap_leaderboard_hourly for fast leaderboard queries
  • Unique constraint on event_id for idempotency

Background Jobs

ZapIngesterJob

  • Connects to 30+ high-coverage Nostr relays
  • Subscribes to kind 9735 (zap receipts) in real-time
  • Parses bolt11 invoices for exact amounts
  • Extracts zapper pubkey from description (zap request)
  • Auto-reconnects on failure
  • Backfills last 10 minutes on restart

ProfileResolverJob

  • Fetches kind 0 metadata from relays
  • Caches for 24 hours
  • Retry logic with backoff (max 5 attempts)
  • Rate limiting to avoid relay abuse

🛠️ Local Development Setup

Prerequisites

  • Docker & Docker Compose
  • Ruby 3.4.7 (or use Docker)
  • PostgreSQL 16 with TimescaleDB (or use Docker)

Quick Start

# 1. Clone the repository
git clone https://github.com/yourusername/zaprank.git
cd zaprank

# 2. Install dependencies
bundle install

# 3. Copy environment file
cp .env.example .env

# 4. Generate secrets
bundle exec rails secret  # Copy to .env as SECRET_KEY_BASE

# 5. Start PostgreSQL with TimescaleDB (using Docker)
docker-compose up -d postgres

# 6. Setup database
bin/rails db:create db:migrate db:seed

# 7. Start Rails server
bin/dev

# 8. Start zap ingester (in another terminal)
bin/rails runner 'ZapIngesterJob.perform_now'

Visit http://localhost:3000


🐳 Production Deployment (VPS with Docker)

Server Requirements

  • Ubuntu 24.04 LTS
  • 4GB RAM minimum (8GB recommended)
  • 50GB SSD (grows with zap data)
  • Docker & Docker Compose installed

Deploy in < 10 Minutes

# 1. SSH into your VPS
ssh [email protected]

# 2. Install Docker (if not installed)
curl -fsSL https://get.docker.com | sh
systemctl enable --now docker

# 3. Clone repository
git clone https://github.com/yourusername/zaprank.git
cd zaprank

# 4. Setup environment
cp .env.example .env
nano .env  # Fill in your values

# Generate secrets:
docker run --rm ruby:3.4-slim ruby -e "require 'securerandom'; puts SecureRandom.hex(64)"
# Copy to .env as SECRET_KEY_BASE and DATABASE_PASSWORD

# 5. Start all services
docker-compose up -d

# 6. Setup database
docker-compose exec app bin/rails db:prepare
docker-compose exec app bin/rails db:migrate
docker-compose exec app bin/rails db:seed

# 7. Check logs
docker-compose logs -f app
docker-compose logs -f worker

Setup Caddy for HTTPS

# 1. Install Caddy
apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | tee /etc/apt/sources.list.d/caddy-stable.list
apt update && apt install caddy

# 2. Copy Caddyfile
cp Caddyfile /etc/caddy/Caddyfile

# 3. Update domain in Caddyfile
nano /etc/caddy/Caddyfile
# Change "zaprank.com" to your domain

# 4. Start Caddy
systemctl enable --now caddy
systemctl status caddy

Your site should now be live at https://your-domain.com with automatic HTTPS!


📊 Database: TimescaleDB

TimescaleDB is automatically enabled by the init-db.sh script. The migrations create the hypertable and continuous aggregates.

Performance

With TimescaleDB hypertables and continuous aggregates:

  • Leaderboard queries: < 80ms at 500M+ rows
  • Daily aggregates: < 50ms
  • Profile stats: < 30ms

🔧 Running the Zap Ingester

The ingester runs as a Solid Queue job via the worker container.

Production (Docker)

# Check worker logs
docker-compose logs -f worker

# Restart worker
docker-compose restart worker

Manual Testing

# Run ingester manually
bin/rails runner 'ZapIngesterJob.perform_now'

📈 Monitoring

Application Logs

# Docker
docker-compose logs -f app
docker-compose logs -f worker

# Local
tail -f log/production.log

🙏 Acknowledgments

  • Nostr: The decentralized social protocol
  • Lightning Network: Bitcoin's layer 2 payment network
  • TimescaleDB: Time-series PostgreSQL extension
  • Rails 8: The best web framework
  • Hotwire: For making SPAs unnecessary
  • Caddy: The best reverse proxy

Built with ⚡ and ❤️ for the Nostr community

Zap responsibly

About

Production-ready Nostr zap leaderboard - Rails 8 + TimescaleDB + Hotwire

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors