'handleNostrLoggedIn', 'nostrLoggedOut' => 'handleNostrLoggedOut', ]; public function mount(): void { if (NostrAuth::check()) { $this->currentPubkey = NostrAuth::pubkey(); $this->currentPleb = EinundzwanzigPleb::query() ->with([ 'paymentEvents' => fn ($query) => $query->where('year', date('Y')), 'profile', ]) ->where('pubkey', $this->currentPubkey)->first(); if ($this->currentPleb) { $this->email = $this->currentPleb->email; if ($this->currentPleb->nip05_handle) { $this->nip05Handle = $this->currentPleb->nip05_handle; // Get all NIP-05 handles for the current pubkey $this->nip05VerifiedHandles = $this->getNip05HandlesForPubkey($this->currentPubkey); if (count($this->nip05VerifiedHandles) > 0) { $this->nip05Verified = true; $this->nip05VerifiedHandle = $this->nip05VerifiedHandles[0]; // Check if verified handle differs from database handle if (! in_array($this->nip05Handle, $this->nip05VerifiedHandles, true)) { $this->nip05HandleMismatch = true; } } } $this->no = $this->currentPleb->no_email; $this->showEmail = ! $this->no; $this->amountToPay = config('app.env') === 'production' ? 21000 : 1; if ($this->currentPleb->paymentEvents->count() < 1) { $this->createPaymentEvent(); $this->currentPleb->load('paymentEvents'); } $this->loadEvents(); $this->listenForPayment(); } } } public function updatedNo(): void { $this->showEmail = ! $this->no; $this->currentPleb->update([ 'no_email' => $this->no, ]); } public function updatedFax(): void { $this->js('alert("Markus Turm wird sich per Fax melden!")'); } public function updatedNip05Handle(): void { $this->nip05Handle = strtolower($this->nip05Handle); } public function saveEmail(): void { $this->validate([ 'email' => 'required|email', ]); $this->currentPleb->update([ 'email' => $this->email, ]); Flux::toast('E-Mail Adresse gespeichert.'); } public function saveNip05Handle(): void { $this->validate([ 'nip05Handle' => 'required|string|max:255|regex:/^[a-z0-9_-]+$/|unique:einundzwanzig_plebs,nip05_handle', ]); $nip05Handle = strtolower($this->nip05Handle); $this->currentPleb->update([ 'nip05_handle' => $nip05Handle, ]); Flux::toast('NIP-05 Handle gespeichert.'); } public function pay($comment): mixed { $paymentEvent = $this->currentPleb ->paymentEvents() ->where('year', date('Y')) ->first(); if ($paymentEvent->btc_pay_invoice) { return redirect()->away('https://pay.einundzwanzig.space/i/'.$paymentEvent->btc_pay_invoice); } try { $response = \Illuminate\Support\Facades\Http::withHeaders([ 'Authorization' => 'token '.config('services.btc_pay.api_key'), ])->post( 'https://pay.einundzwanzig.space/api/v1/stores/98PF86BoMd3C8P1nHHyFdoeznCwtcm5yehcAgoCYDQ2a/invoices', [ 'amount' => $this->amountToPay, 'metadata' => [ 'orderId' => $comment, 'orderUrl' => url()->route('association.profile'), 'itemDesc' => 'Mitgliedsbeitrag '.date('Y').' von nostr:'.$this->currentPleb->npub, 'posData' => [ 'event' => $paymentEvent->event_id, 'pubkey' => $this->currentPleb->pubkey, 'npub' => $this->currentPleb->npub, ], ], 'checkout' => [ 'expirationMinutes' => 60 * 24, 'redirectURL' => url()->route('association.profile'), 'redirectAutomatically' => true, 'defaultLanguage' => 'de', ], ], )->throw(); $paymentEvent->btc_pay_invoice = $response->json()['id']; $paymentEvent->save(); return redirect()->away($response->json()['checkoutLink']); } catch (\Exception $e) { Flux::toast( 'Fehler beim Erstellen der Rechnung. Bitte versuche es später erneut: '.$e->getMessage(), variant: 'danger', ); return redirect()->route('association.profile'); } } public function listenForPayment(): void { $paymentEvent = $this->currentPleb ->paymentEvents() ->where('year', date('Y')) ->first(); if ($paymentEvent->btc_pay_invoice) { $response = \Illuminate\Support\Facades\Http::withHeaders([ 'Authorization' => 'token '.config('services.btc_pay.api_key'), ]) ->get( 'https://pay.einundzwanzig.space/api/v1/stores/98PF86BoMd3C8P1nHHyFdoeznCwtcm5yehcAgoCYDQ2a/invoices/'.$paymentEvent->btc_pay_invoice, ); if ($response->json()['status'] === 'Expired') { $paymentEvent->btc_pay_invoice = null; $paymentEvent->paid = false; $paymentEvent->save(); } if ($response->json()['status'] === 'Settled') { $paymentEvent->paid = true; $paymentEvent->save(); $this->currentYearIsPaid = true; } } if ($paymentEvent->paid) { $this->currentYearIsPaid = true; } $paymentEvent = $paymentEvent->refresh(); $this->payments = $this->currentPleb ->paymentEvents() ->where('paid', true) ->get(); } public function save($type): void { $this->form->validate(); if (! $this->form->check) { $this->js('alert("Du musst den Statuten zustimmen.")'); return; } $this->currentPleb ->update([ 'association_status' => $type, ]); } public function createPaymentEvent(): void { $note = new NostrEvent; $note->setKind(32121); $note->setContent( 'Dieses Event dient der Zahlung des Mitgliedsbeitrags für das Jahr '.date( 'Y', ).'. Bitte bezahle den Betrag von '.number_format($this->amountToPay, 0, ',', '.').' Satoshis.', ); $note->setTags([ ['d', $this->currentPleb->pubkey.','.date('Y')], ['zap', 'daf83d92768b5d0005373f83e30d4203c0b747c170449e02fea611a0da125ee6', config('services.relay'), '1'], ]); $signer = new Sign; $signer->signEvent($note, config('services.nostr')); $eventMessage = new EventMessage($note); $relayUrl = config('services.relay'); $relay = new Relay($relayUrl); $relay->setMessage($eventMessage); $result = $relay->send(); $this->currentPleb->paymentEvents()->create([ 'year' => date('Y'), 'event_id' => $result->eventId, 'amount' => $this->amountToPay, ]); } public function loadEvents(): void { $subscription = new Subscription; $subscriptionId = $subscription->setId(); $filter1 = new Filter; $filter1->setKinds([32121]); $filter1->setAuthors(['daf83d92768b5d0005373f83e30d4203c0b747c170449e02fea611a0da125ee6']); $filters = [$filter1]; $requestMessage = new RequestMessage($subscriptionId, $filters); $relays = [ new Relay(config('services.relay')), ]; $relaySet = new RelaySet; $relaySet->setRelays($relays); $request = new Request($relaySet, $requestMessage); $response = $request->send(); $this->events = collect($response[config('services.relay')]) ->map(function ($event) { if (! isset($event->event)) { return false; } return [ 'id' => $event->event->id, 'kind' => $event->event->kind, 'content' => $event->event->content, 'pubkey' => $event->event->pubkey, 'tags' => $event->event->tags, 'created_at' => $event->event->created_at, ]; }) ->filter() ->unique('id') ->toArray(); } public function copyRelayUrl(): void { $relayUrl = 'wss://nostr.einundzwanzig.space'; $this->js("navigator.clipboard.writeText('{$relayUrl}')"); Flux::toast('Relay-Adresse in die Zwischenablage kopiert!'); } public function copyWatchtowerUrl(): void { $watchtowerUrl = '03a09f56bba3d2c200cc55eda2f1f069564a97c1fb74345e1560e2868a8ab3d7d0@62.171.139.240:9911'; $this->js("navigator.clipboard.writeText('{$watchtowerUrl}')"); Flux::toast('Watchtower-Adresse in die Zwischenablage kopiert!'); } } ?>

Einundzwanzig ist, was du draus machst

Vorteile deiner Mitgliedschaft

Nostr Relay

Exklusive Schreib-Rechte auf Premium Nostr Relay von Einundzwanzig.

@if($currentPleb && $currentPleb->association_status->value > 1 && $currentYearIsPaid)

Ein Outbox-Relay ist wie ein Postbote für deine Nostr-Nachrichten. Es speichert und verteilt deine Posts. Um unser Relay nutzen zu können, musst du es in deinem Nostr-Client hinzufügen.

Gehe in deinem Nostr-Client zu den Einstellungen (meistens "Settings" oder "Relays") und füge folgende Outbox-Relay-Adresse hinzu:

wss://nostr.einundzwanzig.space

Wichtige Hinweise: Du kannst deine Posts auf mehreren Relays gleichzeitig veröffentlichen. So stellst du sicher, dass deine Inhalte auch über unser Relay erreichbar sind.

@endif

Get NIP-05 verified

Verifiziere deine Identität mit einem menschenlesbaren Nostr-Namen.

@if($currentPleb && $currentPleb->association_status->value > 1 && $currentYearIsPaid)
Dein NIP-05 Handle @einundzwanzig.space
Speichern

Regeln für dein Handle: Nur Kleinbuchstaben (a-z), Zahlen (0-9) und die Zeichen "-" und "_" sind erlaubt. Dein Handle wird automatisch kleingeschrieben.

NIP-05 verifiziert deine Identität auf Nostr. Das Handle ist wie eine E-Mail-Adresse (z.B. name@einundzwanzig.space). Clients zeigen ein Häkchen für verifizierte Benutzer. Dies macht dein Profil einfacher zu teilen und vertrauenswürdiger.

@if($nip05Verified)

Du hast {{ count($nip05VerifiedHandles) }} aktive Handles für deinen Pubkey!

@if($nip05HandleMismatch)

Die Synchronisation zu {{ $nip05Handle }}@einundzwanzig.space wird automatisch im Hintergrund durchgeführt.

@endif

Deine aktivierten Handles:

    @foreach($nip05VerifiedHandles as $handle)
  • {{ $handle }}@einundzwanzig.space OK
  • @endforeach
@elseif($nip05Handle)

Dein Handle {{ $nip05Handle }}@einundzwanzig.space ist noch nicht aktiv.

Das Handle ist gespeichert, aber noch nicht in der NIP-05 Konfiguration veröffentlicht. Der Vorstand wird dies bald aktivieren.

@endif
@else
Aktiviere deine Mitgliedschaft, um NIP-05 zu verifizieren.
@endif

Lightning Watchtower

Nutze unseren Watchtower zum Schutz deiner Lightning Channel.

@if($currentPleb && $currentPleb->association_status->value > 1 && $currentYearIsPaid)

Ein Watchtower überwacht deine Lightning Channel und schützt sie, falls deine Node offline ist. Wenn du die Zahlung von Channel-Closing-Transaktionen verpasst, kümmert sich der Watchtower darum und verhindert den Verlust deiner Sats.

Um unseren Watchtower zu nutzen, füge folgende URI in deiner Lightning Node Konfiguration hinzu:

03a09f56bba3d2c200cc55eda2f1f069564a97c1fb74345e1560e2868a8ab3d7d0@62.171.139.240:9911

Einrichtung für gängige Lightning Clients:

  • LND: https://docs.lightning.engineering/lightning-network-tools/lnd/watchtower
  • Core Lightning: Nutze den watchtower-client Plugin mit der URI
  • Eclair: Füge die URI zu den Watchtower-Einstellungen in deiner eclair.conf hinzu

Wichtig: Der Watchtower überwacht deine Channel passiv. Er hat keinen Zugriff auf deine privaten Schlüssel oder dein Guthaben.

@else
Aktiviere deine Mitgliedschaft, um den Lightning Watchtower zu nutzen.
@endif
@if($currentPleb)
Avatar

{{ $currentPleb->profile?->display_name ?? $currentPleb->profile?->name ?? 'Unbekannt' }}

@if($currentPleb->profile?->name) {{ $currentPleb->profile->name }} @endif

Pubkey: {{ $currentPleb->pubkey }}
Npub: {{ $currentPleb->npub }}
@if($currentPleb->nip05_handle)
NIP-05: {{ $currentPleb->nip05_handle }}
@endif
@endif @if(!$currentPleb)

Empfohlene Nostr Login und Signer-Apps

Amber

Perfekt für mobile Android Geräte. Eine App, in der man alle Keys/nsecs verwalten kann.

Android
Alby - Bitcoin Lightning Wallet & Nostr

Browser-Erweiterung in die man seinen Key/nsec eingeben kann. Pro Alby-Konto ein nsec.

Browser Chrome/Firefox
nos2x

Browser-Erweiterung für Chrome Browser. Multi-Key fähig.

Browser Chrome
nos2x-fox

Browser-Erweiterung für Firefox Browser. Multi-Key fähig.

Browser Firefox
@if($currentPubkey && $currentPleb->association_status->value < 2)

Profil in der Datenbank vorhanden.

@endif
@endif @if($currentPubkey && !$currentPleb->application_for && $currentPleb->association_status->value < 2)

Einundzwanzig Mitglied werden

Nur Personen können Mitglied werden und zahlen 21.000 Satoshis im Jahr. Firmen melden sich bitte direkt an den Vorstand.

Mit deinem aktuellen Nostr-Profil Mitglied werden Statuten ansehen
@endif @if($currentPubkey)

Falls du möchtest, kannst du hier eine E-Mail Adresse hinterlegen, damit der Verein dich darüber informieren kann, wenn es Neuigkeiten gibt.

Am besten eine anonymisierte E-Mail Adresse verwenden. Wir sichern diese Adresse AES-256 verschlüsselt in der Datenbank ab.

@if(!$no)
Fax-Nummer E-Mail Adresse
Speichern
@endif
@endif
@if($currentPleb && $currentPleb->association_status->value > 1)

@if($currentYearIsPaid) Du bist derzeit ein Mitglied des Vereins. Das aktuelle Jahr ist bezahlt. @else Du wirst nach Zahlung des Vereinsbeitrages zum Mitglied. Das aktuelle Jahr ist noch nicht bezahlt. @endif

@endif @if($currentPleb && $currentPleb->association_status->value > 1)

Mitgliedsbeitrag

Nostr Event für die Zahlung des Mitgliedsbeitrags: {{ $currentPleb->paymentEvents->last()->event_id }}

@php $latestEvent = collect($events)->sortByDesc('created_at')->first(); @endphp @if(isset($latestEvent))

{{ $latestEvent['content'] }}

@if(!$currentYearIsPaid) Pay {{ $amountToPay }} Sats @else Aktuelles Jahr bezahlt @endif
@else

Unser Nostr-Relay konnte derzeit nicht erreicht werden, um eine Zahlung zu initialisieren. Bitte versuche es später noch einmal.

@endif
@if($payments && count($payments) > 0)

Bisherige Zahlungen

@foreach($payments as $payment)
Satoshis {{ $payment->amount }}
Jahr {{ $payment->year }}
Event-ID {{ $payment->event_id }}
@if($payment->btc_pay_invoice) Quittung anzeigen @endif
@endforeach
@endif
@endif