Skip to content

Latest commit

 

History

History
416 lines (326 loc) · 12.1 KB

File metadata and controls

416 lines (326 loc) · 12.1 KB

🏗️ Satsplit Architecture

Overview

Satsplit es una plataforma de bounties colaborativos construida 100% sobre Nostr + Lightning, sin base de datos centralizada. Toda la data vive encriptada en relays mediante NIP-44.


🧩 Componentes

1. Frontend (/frontend)

  • Framework: React 19 + TypeScript + Vite
  • Nostr: nostr-tools (NIP-07, NIP-46 login)
  • Responsabilidades:
    • UI/UX para bounties (lista, crear, fondear, resolver)
    • Autenticación Nostr (window.nostr, nsecBunker)
    • Comunicación con backend API

2. Backend (/backend)

  • Runtime: Node.js 22+ con Express + TypeScript
  • Nostr: nostr-tools (SimplePool, NIP-44 encryption)
  • Responsabilidades:
    • Encriptar/desencriptar data con NIP-44
    • Publicar eventos en relays
    • Gestionar NWC (Nostr Wallet Connect) para pagos
    • API REST para frontend

📦 Data Model (Encrypted Nostr Events)

Todos los eventos se publican con el pubkey del backend (derivado de BACKEND_NSEC). El contenido (content) está encriptado con NIP-44.

Event Kinds

Kind Tipo Descripción Tags
30078 Bounty Bounty individual (parametrized replaceable) d, title, status, creator
30079 Contribution Registro de fondeo (zap receipt) d, bounty_id, amount, npub
30080 Distribution Distribución de premio (splits ejecutados) d, bounty_id, splits
30081 Config Configuración global (admin + NWC) d = config

Bounty Event (kind: 30078)

Tags públicos (no encriptados):

{
  "kind": 30078,
  "pubkey": "<backend_pubkey>",
  "tags": [
    ["d", "<bounty_id>"],        // unique ID
    ["title", "Fix bug #123"],   // título público
    ["status", "open"],          // open | funded | in-progress | completed | cancelled
    ["creator", "<npub>"]        // creador del bounty
  ],
  "content": "<encrypted_json>",
  "created_at": 1234567890
}

Content encriptado (NIP-44):

{
  id: string,
  title: string,
  description: string,
  creator: string,              // npub
  target: number | null,        // sats (opcional)
  status: BountyStatus,
  contributors: [
    { npub: string, amount: number, timestamp: number }
  ],
  submissions: [
    { npub: string, pr?: string, description: string, timestamp: number }
  ],
  distributions: [
    { npub: string, percentage: number, amount: number }
  ],
  total_funded: number,
  created_at: number,
  completed_at?: number
}

Config Event (kind: 30081)

Tags públicos:

{
  "kind": 30081,
  "pubkey": "<backend_pubkey>",
  "tags": [
    ["d", "config"]
  ],
  "content": "<encrypted_json>",
  "created_at": 1234567890
}

Content encriptado:

{
  admin_npub: string,     // npub del admin (único con permisos)
  nwc_uri: string,        // nostr+walletconnect://... (tesoro)
  treasury_balance?: number,
  relays: string[],
  created_at: number
}

🔐 Encriptación (NIP-44)

Clave Privada del Backend

El backend tiene su propio nsec (variable BACKEND_NSEC). Esta clave se usa para:

  1. Firmar todos los eventos publicados
  2. Encriptar data sensible (bounties, config)
  3. Desencriptar data al leer de relays

Flujo de Encriptación

// Encriptar
const conversationKey = nip44.v2.utils.getConversationKey(
  backendPrivkey,
  recipientPubkey  // mismo backend pubkey para self-encryption
);
const ciphertext = nip44.v2.encrypt(JSON.stringify(data), conversationKey);

// Desencriptar
const plaintext = nip44.v2.decrypt(ciphertext, conversationKey);
const data = JSON.parse(plaintext);

Self-encryption: El backend encripta con su propia pubkey como "recipient", permitiendo que solo el backend pueda leer la data.


⚡ NWC (Nostr Wallet Connect)

Tesoro Centralizado

Satsplit usa un solo NWC URI configurado por el admin. Este wallet actúa como "tesoro" del sistema:

  • Recibe todos los fondos de contribuciones
  • Paga todas las distribuciones cuando un bounty se completa

NWC URI Format

nostr+walletconnect://<pubkey>?relay=<relay>&secret=<secret>

Ejemplo:

nostr+walletconnect://69effe7b49a6dd5cf525bd0905917a5005ffe480b58eeb8e861bb4168e5142acc?relay=wss://relay.getalby.com/v1&secret=secret123

Operaciones NWC

El backend implementa las siguientes operaciones (NIP-47):

Método Descripción Uso en Satsplit
make_invoice Crear invoice para recibir pago Fondear bounty
pay_invoice Pagar un invoice existente Distribuir splits (uno por colaborador)
get_balance Consultar balance del wallet Admin dashboard
multi_payment Pagar múltiples invoices (batch) Distribución eficiente de splits

Flujo de Pago

1. Fondear Bounty:

Usuario → Frontend: "Fondear 10,000 sats"
Frontend → Backend: POST /bounties/:id/fund { amount: 10000 }
Backend → NWC: make_invoice(10000, "Bounty: Fix bug #123")
NWC → Backend: invoice (lnbc...)
Backend → Frontend: { invoice: "lnbc..." }
Frontend → Usuario: Mostrar QR / pagar con wallet
Usuario → Lightning: Paga invoice
Fondos → Tesoro NWC

2. Distribuir Premio:

Creador → Frontend: Define splits (60% A, 40% B)
Frontend → Backend: POST /bounties/:id/complete { splits: [...] }
Backend: Calcula montos (6k sats, 4k sats)
Backend → NWC: multi_payment([
  { invoice: "lnbc6000...", amount: 6000 },  // A's invoice
  { invoice: "lnbc4000...", amount: 4000 }   // B's invoice
])
NWC → Lightning: Paga ambos invoices
Backend: Actualiza bounty.status = "completed"
Backend → Nostr: Publica evento kind:30080 (distribution)

🔄 Flujos Principales

Setup Inicial

sequenceDiagram
    Frontend->>Backend: GET /setup/status
    Backend->>Nostr: Buscar kind:30081 (config)
    Nostr-->>Backend: No encontrado
    Backend-->>Frontend: { setup_complete: false }
    Frontend->>Usuario: "Primera vez: conectar con Nostr"
    Usuario->>Frontend: Login con NIP-07 (npub1abc...)
    Frontend->>Backend: POST /setup/init { admin_npub: "npub1abc..." }
    Backend->>Nostr: Publica kind:30081 (config con admin)
    Backend-->>Frontend: { success: true }
    Frontend->>Usuario: "Ingresar NWC URI del tesoro"
    Usuario->>Frontend: Pega NWC URI
    Frontend->>Backend: POST /setup/nwc { nwc_uri: "nostr+walletconnect://..." }
    Backend->>NWC: Verificar conexión (get_balance)
    NWC-->>Backend: Balance OK
    Backend->>Nostr: Actualiza kind:30081 (config con NWC)
    Backend-->>Frontend: { success: true }
    Frontend->>Usuario: "Setup completo! 🎉"
Loading

Crear + Fondear Bounty

sequenceDiagram
    Usuario->>Frontend: Crear bounty
    Frontend->>Backend: POST /bounties { title, description, target, creator_npub }
    Backend->>Backend: Genera bounty_id único
    Backend->>Backend: Encripta bounty (NIP-44)
    Backend->>Nostr: Publica kind:30078
    Backend-->>Frontend: { bounty }
    Frontend-->>Usuario: "Bounty creado!"
    
    Funder->>Frontend: Fondear 10k sats
    Frontend->>Backend: POST /bounties/:id/fund { amount: 10000 }
    Backend->>Nostr: Lee config (NWC URI)
    Backend->>NWC: make_invoice(10000, "Bounty XYZ")
    NWC-->>Backend: invoice
    Backend-->>Frontend: { invoice }
    Frontend-->>Funder: Mostrar QR
    Funder->>Lightning: Paga invoice
    Lightning->>NWC: Fondos recibidos
    Backend->>Backend: Actualiza bounty.total_funded += 10000
    Backend->>Nostr: Publica kind:30078 actualizado
Loading

Completar + Distribuir

sequenceDiagram
    Creador->>Frontend: Completar bounty con splits
    Frontend->>Backend: POST /bounties/:id/complete { splits: [...] }
    Backend->>Backend: Valida splits suman 100%
    Backend->>Backend: Calcula montos
    Backend->>Nostr: Lee NWC URI de config
    Backend->>NWC: multi_payment([...invoices])
    NWC->>Lightning: Paga splits
    Lightning-->>Colaboradores: Reciben sats
    Backend->>Backend: bounty.status = "completed"
    Backend->>Nostr: Publica kind:30078 (bounty completado)
    Backend->>Nostr: Publica kind:30080 (distribution record)
    Backend-->>Frontend: { success: true, payments }
    Frontend-->>Creador: "Pagos enviados! ⚡"
Loading

🛡️ Seguridad

Backend Privkey Protection

  • BACKEND_NSEC en variable de entorno (.env)
  • ✅ Nunca exponerlo via API
  • ✅ Solo backend puede encriptar/desencriptar
  • ⚠️ Si se compromete: toda la data histórica es legible

Admin Verification

// Middleware requireAdmin
const config = await fetchConfig();
if (config.admin_npub !== req.body.admin_npub) {
  return res.status(403).json({ error: 'Unauthorized' });
}
  • Solo el admin_npub puede:
    • Ver balance del tesoro
    • Modificar config
    • Acceder a /admin/* endpoints

NWC URI Storage

  • ✅ Encriptado con NIP-44 en config event
  • ✅ Solo backend puede desencriptar
  • ✅ Nunca retornado via API (ni siquiera a admin)
  • ⚠️ Admin debe guardar backup del URI externamente

Relays Trust

  • Data encriptada → relays solo ven ciphertext
  • Relays pueden retener eventos pero no leerlos
  • Relays pueden censurar (solución: usar múltiples)
  • Relays pueden perder data (solución: backup manual)

📊 API Reference

Setup

GET /setup/status
Response: { setup_complete: boolean, admin_npub?: string, has_nwc?: boolean }

POST /setup/init
Body: { admin_npub: string }
Response: { success: true, admin_npub: string }

POST /setup/nwc
Body: { nwc_uri: string }
Response: { success: true }

Bounties

GET /bounties
Response: { bounties: Bounty[] }

GET /bounties/:id
Response: { bounty: Bounty }

POST /bounties
Body: { title, description, target?, creator_npub }
Response: { success: true, bounty: Bounty }

POST /bounties/:id/fund
Body: { amount: number }
Response: { success: true, invoice: string, amount: number }

POST /bounties/:id/submit
Body: { npub, description, pr? }
Response: { success: true, bounty: Bounty }

POST /bounties/:id/complete
Body: { splits: { npub, percentage }[] }
Response: { success: true, bounty: Bounty, payments: PaymentResponse[] }

Admin

GET /admin/balance
Body: { admin_npub }  // auth
Response: { balance: number, unit: "sats" }

GET /admin/config
Body: { admin_npub }  // auth
Response: { admin_npub, has_nwc, relays, created_at }

🚧 Limitaciones Actuales (MVP)

  1. NWC mock: Pagos simulados (falta implementar NIP-47 completo)
  2. Invoice generation: Usuarios deben generar invoices manualmente
  3. Zap receipts: No se escuchan automáticamente (falta listener)
  4. Balance tracking: No se actualiza en tiempo real
  5. Frontend incompleto: Solo estructura base del boilerplate

🗺️ Próximos Pasos

Inmediato (Sprint 1)

  1. Implementar NWC real (NIP-47 completo)
  2. Listener de zap receipts (NIP-57)
  3. Frontend: lista de bounties
  4. Frontend: crear bounty form
  5. Frontend: fondear con invoice

Corto plazo (Sprint 2-3)

  1. Sistema de submissions
  2. Distribución funcional
  3. Dashboard admin
  4. Notificaciones

Mediano plazo

  1. Milestone-based bounties
  2. GitHub integration
  3. Reputation system
  4. Mobile app (React Native)

📚 Referencias


Bitcoin or Death. 💀

Powered by La Crypta