No description
| src | ||
| static | ||
| .clinerules | ||
| .dockerignore | ||
| .env.example | ||
| .gitignore | ||
| docker-compose.yml | ||
| Dockerfile | ||
| LICENSE | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
| svelte.config.js | ||
| vite.config.js | ||
🦡 Badger - Nostr Badge System
A decentralized badge management system built on the Nostr protocol, implementing NIP-58.
Features
- ✅ Create Badges - Design and publish badge definitions (kind 30009)
- ✅ Award Badges - Award badges to Nostr users (kind 8)
- ✅ Accept Badges - Users can accept or reject badge awards (kind 30008)
- ✅ View Gallery - Browse all badges on the network
- ✅ NIP-07 Authentication - Secure login via browser extensions (Alby, nos2x, etc.)
Tech Stack
- Svelte 5 - Modern reactive framework with runes
- SvelteKit - Full-stack framework
- Applesauce - Nostr event management and relay communication
- Tailwind CSS + daisyUI - Beautiful, responsive UI
- RxJS - Reactive data streams
- Nostr Tools - Nostr protocol utilities
Prerequisites
- Node.js 18+
- A Nostr browser extension (Alby, nos2x, Nostore, etc.)
Installation
# Install dependencies
npm install
# Start development server
npm run dev
# Build for production
npm run build
# Preview production build
npm run preview
Deployment (Docker + Traefik)
Prerequisites
- Docker and Docker Compose
- Traefik reverse proxy with:
- External network named
traefik_web - Certificate resolver named
myresolver
- External network named
Setup
- Create environment file:
cp .env.example .env
- Configure
.env:
NODE_ENV=production
PORT=3000
ORIGIN=https://badger.example.com
DOMAIN=badger.example.com
- Build and start:
docker compose up -d --build
- View logs:
docker compose logs -f badger
Environment Variables
| Variable | Required | Default | Description |
|---|---|---|---|
NODE_ENV |
No | production |
Node environment |
PORT |
No | 3000 |
Internal port |
ORIGIN |
Yes | - | Full URL (e.g., https://badger.example.com) |
DOMAIN |
Yes | - | Domain for Traefik routing |
Usage
1. Authentication
Click "Login with Nostr" in the navigation bar. Your browser extension will prompt you to approve the connection.
2. Creating Badges
- Click "Create Badge" in the navigation
- Fill in the badge details:
- Identifier: Unique ID (lowercase, no spaces)
- Name: Display name for the badge
- Description: What the badge represents
- Image URL: Optional badge icon (hosted externally)
- Click "Create Badge" - your extension will prompt you to sign the event
- Badge will be published to relays and appear in the gallery
3. Viewing Badges
The main page displays:
- All badges created on the network
- Your awarded badges (if any)
- Badge details (name, description, creator)
4. Badge Awards
When a badge creator awards you a badge (kind 8 event), you'll see it in your awards section.
5. Accepting Badges
Users can accept badges to display them publicly by publishing a kind 30008 event referencing the badge and award.
Architecture
Three-Layer Pattern (Applesauce)
┌─────────────────────────────────────────┐
│ Presentation (Svelte Components) │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ Models (Data Transformation) │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ Loaders (Relay Communication) │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ Infrastructure (EventStore + Pool) │
└─────────────────────────────────────────┘
Key Files
src/lib/stores/nostr-infrastructure.svelte.js- EventStore, RelayPool, loaderssrc/lib/stores/auth.svelte.js- Authentication statesrc/lib/models/- Badge, Award, and Profile Badge modelssrc/routes/+page.svelte- Badge gallerysrc/routes/create/+page.svelte- Badge creation form
NIP-58 Implementation
Badge Definition (kind 30009)
{
kind: 30009,
tags: [
['d', 'unique-identifier'],
['name', 'Badge Name'],
['description', 'Badge description'],
['image', 'https://example.com/badge.png', '1024x1024']
]
}
Badge Award (kind 8)
{
kind: 8,
tags: [
['a', '30009:creator_pubkey:badge_id'],
['p', 'recipient_pubkey']
]
}
Profile Badges (kind 30008)
{
kind: 30008,
tags: [
['d', 'profile_badges'],
['a', '30009:creator_pubkey:badge_id'],
['e', 'award_event_id']
]
}
Default Relays
- wss://relay.damus.io
- wss://nos.lol
- wss://relay.snort.social
Development Notes
Critical Patterns
- Loader Bootstrap Order - Loaders must be connected to EventStore before using models
- Progressive Streaming - Use
scanoperator to prevent UI blocking - Subscription Cleanup - Always unsubscribe in
$effectcleanup functions - Reactive State - Use Svelte 5 runes (
$state,$derived,$effect)
Adding Features
To add new badge-related features:
- Create a model in
src/lib/models/ - Use the model in components via
eventStore.model() - Subscribe to updates and clean up properly
License
Public Domain https://unlicense.org/