mroxso 8d594d7d69 Add Lightning zaps with optional Nostr Wallet Connect (#62)
* Add Lightning zaps with optional Nostr Wallet Connect

Signed-in users can zap a note or reply, and see its zap total, from
the feed, a thread view, and replies. Zapping opens a dialog to pick
an amount (presets or custom) and an optional comment, then:

- Builds and signs a NIP-57 zap request and fetches an invoice from
  the recipient's LNURL/lud16 callback (rejecting non-https endpoints).
- If a wallet is connected via Nostr Wallet Connect (NIP-47, Settings
  > Lightning wallet), pays the invoice automatically and only reports
  success once the wallet returns a payment preimage.
- Otherwise shows the invoice as a QR code plus a copy/`lightning:`
  link for the user's own wallet, and polls for a matching zap receipt
  to confirm payment without ever asserting success it can't verify.

Zap totals sum kind-9735 receipts defensively: a receipt only counts
if it carries a bolt11 amount and a description whose embedded zap
request is a well-formed, signature-valid event, so a malformed or
forged receipt can't inflate the total.

The NWC connection secret is stored only in this browser, scoped to
the signed-in pubkey, and is used solely to sign/send payment requests
to the wallet's own relay — never published, logged, or shown besides
a truncated pubkey once connected.

Closes #54

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BYiUtZMQeA5RHggQw73wto

* Address PR #62 review feedback: NWC relay security, N+1 zap queries, manual-payment confirmation

Addresses Copilot review comments on PR #62:

- nwc.ts: reject plaintext ws:// Wallet Connect relays, requiring wss://.
  Every request is signed by the connection secret (a private key); even
  with an encrypted payload, an unencrypted transport still leaks metadata
  about the connection and admits tampering.
- ZapButton/useZaps: fixed the N+1 query pattern — a feed page mounted one
  unconditional zap-receipts query (limit 500) per rendered note. Added a
  `revealed` gate so the query only fires once a note is actually hovered
  or focused (the same interaction that already reveals the action row via
  CSS), confirmed live: 0 queries fired across 49 mounted notes before any
  interaction, exactly 1 after hovering one.
- ZapDialog: manual-payment confirmation compared the note's total receipt
  *count* against a baseline, so anyone else zapping the same note while
  the dialog waited would falsely confirm the viewer's own unpaid invoice.
  Added `hasValidReceiptForInvoice` to match against the specific invoice
  instead, with unit tests covering the exact race the review described.
- useZaps.ts: corrected a docstring overclaiming that receipt validation
  prevents "forged" receipts from inflating totals — it only rules out
  structurally invalid data; NIP-57 receipts are vouched for by the
  recipient's own LNURL server, so trusting one is inherent to the
  protocol, not something client-side validation can prove.

Investigated but did not change: the review's claim that
`nip04.decrypt(...)` needs an `await` because it returns a Promise. Not
correct for this project's actual `nostr-tools` dependency — confirmed by
running the real encrypt/decrypt round trip, `decrypt` is synchronous and
returns the plaintext string directly, so the existing `JSON.parse` call
already worked.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BYiUtZMQeA5RHggQw73wto

* Merge main and resolve action conflicts

Co-authored-by: mroxso <24775431+mroxso@users.noreply.github.com>

---------

Co-authored-by: highperfocused <highperfocused@pm.me>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-09-07 17:16:10 +02:00
2026-09-06 11:37:43 +02:00
2025-12-27 21:42:31 +01:00
2025-12-27 21:42:31 +01:00
2026-09-06 11:37:43 +02:00
2026-09-06 11:37:43 +02:00
2025-12-27 21:42:31 +01:00
2025-12-27 21:42:31 +01:00
2026-09-06 11:37:43 +02:00
2026-09-06 11:37:43 +02:00
2026-09-06 11:37:43 +02:00
2026-09-06 11:37:43 +02:00
2026-09-06 11:37:43 +02:00
2026-09-06 11:37:43 +02:00
2026-09-06 11:37:43 +02:00
2026-09-06 11:37:43 +02:00
2026-09-06 11:37:43 +02:00

LAYER.systems

LAYER.systems is a Nostr client designed like a desktop operating system. Instead of navigating between pages, you open apps in windows that can be moved, resized, stacked, minimized, and kept open side by side. This makes it possible to read a thread, inspect a profile, and keep your feed available at the same time.

The app is for people who use Nostr and want a focused, multitasking-friendly interface, as well as contributors interested in building Nostr applications with React and TypeScript.

What you can do

  • Browse a Following or Global feed, publish notes, and open notes with their replies.
  • View profiles, follow or unfollow people, and search notes, replies, hashtags, and users.
  • Read NIP-23 long-form articles, save notes and articles, and create web bookmarks.
  • Discover picture posts and view live events with their NIP-53 chat.
  • Browse Nostr calendar events by month.
  • Manage relay connections, inspect connection latency, and configure relays and Blossom media servers.
  • Sign in with a Nostr account, switch between accounts, and use NIP-19 identifiers such as npub, note, nevent, nprofile, and naddr as links.
  • Save and share reusable Nostr queries with Spells.

LAYER.systems reads and writes Nostr events through relays. It does not provide a centralized social database; your relays and signer remain the source of your Nostr data. Some features require a signed-in account, and availability of content depends on the relays you use.

Run locally

Requirements

  • Node.js 22 or newer
  • npm

Clone the repository, install dependencies, and start the Vite development server:

git clone https://github.com/layer-systems/website.git
cd website
npm install
npm run dev

The development server listens on http://localhost:8080. The app has no required environment variables; relay and media-server settings are managed in the app.

Useful commands:

npm run test   # Type-check, lint, run Vitest, and create a production build
npm run build  # Create the production bundle in dist/

npm run build also creates dist/404.html, which supports client-side routes when the build is deployed as a static site.

Production deployment

The repository includes a GitHub Actions workflow in .github/workflows/deploy.yml. It runs on pushes to main (or manually from the Actions tab), builds the app with Node.js 22, and deploys dist/ to GitHub Pages.

For another static host, run npm run build and publish the generated dist/ directory. Configure the host to serve dist/404.html for unknown paths so direct NIP-19 links continue to load the client application.

Technology

The application is organized into three main layers:

src/os/              Window manager, persistence, layout, and keyboard shortcuts
src/components/os/   Desktop shell, menu bar, windows, and mobile app shell
src/apps/            Nostr and system apps rendered inside windows
src/components/      Shared UI, authentication, and Nostr components
src/hooks/           Data access and application hooks

Apps are registered in src/os/registry.ts. Each app is lazy loaded and receives a small window-oriented contract, so adding an app normally does not require changing the router. Read the app guide before adding one.

Documentation

For protocol reference, see the Nostr protocol documentation.

Contributing

  1. Create a branch for your change.
  2. Install dependencies with npm install.
  3. Make the smallest focused change that fits the existing architecture.
  4. Run npm run test.
  5. Open a pull request describing the behavior you changed and how you verified it.

When working with Nostr content, treat events, URLs, and profile metadata as untrusted input. Follow the validation and URL-sanitization patterns in docs/nostr.md, and do not use any in TypeScript.

License

LAYER.systems is dedicated to the public domain. You are free to copy, modify, distribute, and use it for any purpose, commercial or non-commercial.

Description
No description provided
Readme 1.6 MiB
Languages
TypeScript 98.1%
CSS 1%
JavaScript 0.8%
HTML 0.1%