package app import ( "encoding/json" "net/http" "strings" "git.mleku.dev/mleku/nostr/encoders/bech32encoding" "git.mleku.dev/mleku/nostr/encoders/hex" "git.mleku.dev/mleku/nostr/interfaces/signer/p8k" "lol.mleku.dev/chk" "lol.mleku.dev/log" ) // BunkerInfoResponse is returned by the /api/bunker/info endpoint. type BunkerInfoResponse struct { RelayURL string `json:"relay_url"` // WebSocket URL for NIP-46 connections RelayNpub string `json:"relay_npub"` // Relay's npub RelayPubkey string `json:"relay_pubkey"` // Relay's hex pubkey ACLMode string `json:"acl_mode"` // Current ACL mode CashuEnabled bool `json:"cashu_enabled"` // Whether CAT is required Available bool `json:"available"` // Whether bunker is available } // handleBunkerInfo returns bunker connection information. // This is a public endpoint that doesn't require authentication. func (s *Server) handleBunkerInfo(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } // Get relay identity relaySecret, err := s.DB.GetOrCreateRelayIdentitySecret() if chk.E(err) { log.E.F("failed to get relay identity: %v", err) http.Error(w, "Failed to get relay identity", http.StatusInternalServerError) return } // Derive public key sign, err := p8k.New() if chk.E(err) { http.Error(w, "Failed to create signer", http.StatusInternalServerError) return } if err := sign.InitSec(relaySecret); chk.E(err) { http.Error(w, "Failed to initialize signer", http.StatusInternalServerError) return } relayPubkey := sign.Pub() relayPubkeyHex := hex.Enc(relayPubkey) // Encode as npub relayNpubBytes, err := bech32encoding.BinToNpub(relayPubkey) relayNpub := string(relayNpubBytes) if chk.E(err) { relayNpub = relayPubkeyHex // Fallback to hex } // Build WebSocket URL from service URL serviceURL := s.ServiceURL(r) wsURL := strings.Replace(serviceURL, "https://", "wss://", 1) wsURL = strings.Replace(wsURL, "http://", "ws://", 1) // Check if Cashu is enabled cashuEnabled := s.CashuIssuer != nil // Bunker is available when ACL mode is not "none" available := s.Config.ACLMode != "none" resp := BunkerInfoResponse{ RelayURL: wsURL, RelayNpub: relayNpub, RelayPubkey: relayPubkeyHex, ACLMode: s.Config.ACLMode, CashuEnabled: cashuEnabled, Available: available, } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(resp) }