mirror of
https://github.com/aljazceru/awesome-nostr.git
synced 2026-10-05 11:28:22 +00:00
GitHub's sanitizer strips lightning: links entirely (verified via the GitHub markdown render API), so chips now use a self-hosted https form: [⚡ zap](https://nostr.net/grant/?zap=addr@host) - GitHub visitors get a working link to the grant page, which auto-opens the zap dialog (QR / copy / open wallet / connect-wallet path) - nostr.net decodes the address locally from the query string; the full in-page zap flow is unchanged; raw lightning: chips still parse (back-compat) - README addresses are human-readable again (trivial to audit); the getalby.com redirect helper is dead (404) so nothing depends on it - zap-core: lnurlDecode (checksum-verified bech32) + tests; 27 pass
143 lines
15 KiB
Markdown
143 lines
15 KiB
Markdown
# grantless — zappable funding for every project on nostr.net
|
||
|
||
**One-liner:** nostr.net keeps being the curated awesome-list, but every project entry can now carry its own lightning address. Any visitor connects their wallet once (Bitcoin Connect / NWC), ticks the projects they want to support, and zaps them all in one go — with **full NIP-57 zap receipts**, so every payment shows up in feeds and on Zaplife. Nostr.NET itself is always pre-ticked as the curator/host. No middlemen, no grant committee, no "one size fits all" — just markdown, PRs, and sats.
|
||
|
||
---
|
||
|
||
## 1. The problem this solves
|
||
|
||
- People have wanted a no-gatekeeper alternative to Geyser/OpenSats for nostr projects *for a while*. `grantless.org` exists but gets no traction — it has no distribution. nostr.net already has the distribution: it's *the* list people link to.
|
||
- Projects on the list have no funding link at all today; discoverability ≠ support.
|
||
- The site itself (relay, curation work) has no obvious "fund this" path.
|
||
|
||
The insight: the list **is** the funding platform. Turn entries into zappable entities without changing the low-tech, markdown-PR workflow that made it the best list in the first place.
|
||
|
||
## 2. The markdown convention (single source of truth)
|
||
|
||
Keep the existing format, add optional standardized chips at the end of an entry:
|
||
|
||
```markdown
|
||
- [Amethyst](https://www.amethyst.social/) - Android-only app [⚡ zap](https://nostr.net/grant/?zap=vitor@amethyst.social) [donate](https://geyser.fund/project/xyz)
|
||
- [Damus](https://damus.io/) - The OG nostr client for iOS [⚡ zap](https://nostr.net/grant/?zap=jb55@damus.io)
|
||
```
|
||
|
||
Rules:
|
||
|
||
- **`[⚡ zap](https://nostr.net/grant/?zap=jb55@damus.io)`** — an https redirect helper carrying the bech32-encoded LNURL-pay endpoint of the project's lightning address. GitHub's sanitizer **strips raw `lightning:` links entirely** (verified via the GitHub markdown render API), so chips must use this https form to stay clickable on GitHub — clicking offers to open the user's wallet (or shows a QR). On nostr.net the chip is decoded **locally** (`zapCore.lnurlDecode`) back into the address and zapped in-page; the helper is never in the zap path itself. Raw `lightning:` chips are still recognized by the parser for back-compat.
|
||
- **`[donate](https://...)`** — optional external funding link (Geyser, OpenSats, Patreon, whatever). Rendered as a second chip. No one-size-fits-all: the project picks.
|
||
- Everything else about entries stays exactly as it is. GitHub renders the chips as normal links, so the README stays perfectly readable on GitHub too.
|
||
- Adding/changing a zap address happens via PR — same review flow as today. That's the "grantless" model: anyone can walk in, but the curator merges.
|
||
|
||
## 3. Site integration (nostr.net itself)
|
||
|
||
1. **Zap chip on every card** — in `createResourceCard()`, parse the raw entry; if a lightning address exists, render a `⚡ Zap` button on the card:
|
||
- Not connected: click opens the **zap dialog** — scannable LNURL QR (machine-verified with a real decoder), the LN address with copy button, an "Open wallet app" `lightning:` link, and a "connect a wallet" path. Zero connection required.
|
||
- Connected: full in-page LNURL-pay + NIP-57 zap (see §5).
|
||
2. **`donate` chip** — simple external link chip.
|
||
3. **Nav zap button** — a small ⚡ in the top nav ("Fund nostr.net") zapping the site's own lightning address: **`aljaz@minibits.cash`** (the relay/curation tip jar).
|
||
4. **"Zappable projects" section on the homepage** — the homepage aggregates every entry carrying a zap chip into a dedicated sidebar section (nostr.net pinned at the top as curator & host). It grows automatically as PRs add addresses — the main list *is* the funding directory. The homepage banner links to this section.
|
||
5. Banner/link on the homepage pointing to the Grant page (§4) for the batch-zap flow.
|
||
|
||
## 4. The Grant page — the "grantless" experience
|
||
|
||
A single static page at **`nostr.net/grant`** (`grant.html` with a Jekyll permalink; no backend, deployable alongside the site; the `grantless.nostr.net` subdomain is a pure DNS/alias question — see §8).
|
||
|
||
UX flow:
|
||
|
||
1. **Load** — page fetches `README.md`, extracts all entries with lightning addresses → grid of zappable projects (name, favicon, category, description). Category filter chips ("clients", "relays", "libraries", …) since we know the section each entry lives in.
|
||
2. **Select** — checkbox per project. Pinned at the top: **nostr.net (curator & host) · `aljaz@minibits.cash`**, checked by default (uncheckable). Quick-select presets: "Select all", "Top projects", per-category.
|
||
3. **Amount** — one "sats per project" field (default 1000), live total = `N × amount`. (v2: per-project override.)
|
||
4. **Connect** — `<bc-button>` from Bitcoin Connect (NWC string, Alby Hub, extension, LNC…).
|
||
5. **Zap** — "Zap 12 projects · 12,000 sats" → sequential loop with visible per-row progress (full NIP-57 flow per project):
|
||
- fetch LNURL-pay params (`https://host/.well-known/lnurlp/user`)
|
||
- build & sign a kind 9734 zap-request event (`p`-tag = recipient `nostrPubkey`; sender via NIP-07 or anonymous — see §5; comment if `commentAllowed`)
|
||
- request invoice with the zap request in the `nostr` param at `amount` msat (clamp to recipient's min/max, surface any clamping)
|
||
- pay via WebLN / NWC `pay_invoice`
|
||
- recipient's server publishes the kind 9735 zap receipt → row shows ✅ with a link to the receipt, ❌ with a "retry" affordance
|
||
- ~400ms pause between zaps (wallet friendliness)
|
||
6. **Summary** — "You zapped N projects, X sats sent, N zap receipts published." + **share on nostr** button (NIP-07): *"I just zapped N nostr projects via nostr.net/grant ⚡"* — the viral loop for the podcast shill.
|
||
|
||
Edge cases: recipient LNURL down or no CORS → fall back to the `lightning:` link (click-to-open-wallet). Recipient LNURL doesn't support zaps (`allowsNostr` false / no `nostrPubkey`) → degrade that row to a plain payment, explicitly labeled "no zap receipt". Invoice below minSendable → clamp with a note. Disconnect mid-batch → stop, keep state, resume after reconnect.
|
||
|
||
## 5. Wallet layer (all client-side)
|
||
|
||
- **Connection:** Bitcoin Connect (`@getalby/bitcoin-connect@3.12.3` via esm.sh, `<bc-button>` in the nav; namespace import → `window.bitcoinConnect`). **Important:** BC does *not* set `window.webln` for NWC/LNC/LNbits — the connected WebLN provider is delivered via `onConnected(provider)` (fires immediately for previously-connected wallets). Both provider sources (extension `window.webln` + BC provider) are unified behind `getProvider()`; `launchModal()` is preferred over clicking inside shadow DOM. **Shipped on the homepage** — this part of Phase 2 landed early. No backend, no custody, no accounts — the site never sees keys.
|
||
- **Full NIP-57 in-page, verified end-to-end** (kind 9734 zap request with `p`/`relays`/`amount`/`lnurl` tags → invoice with `nostr` param → WebLN `sendPayment`). Implementation notes: the UMD CDN build has externalized globals — use the esm.sh module import instead; there is no `bc:connected` window event, so connection is detected by `window.webln` presence (+poll after opening the modal); anonymous zap receipts are signed client-side with `@noble/curves/secp256k1` schnorr (lazy-loaded) when no NIP-07 signer exists.
|
||
- **Sender identity, in order of preference:** ① **anonymous by default** (fast — no signing prompt per zap): ephemeral key + `anon` tag, still a valid zap receipt; after an anon zap, a one-time "zap as me instead" link in the toast opts into ② **attributed zaps** via NIP-07 (remembered in localStorage). Nobody is blocked from receipt-grade zaps by lacking a signer, and extension users don't get a signer popup on every zap.
|
||
- **Payment:** we drive LNURL-pay ourselves (plain `fetch`es) and pay invoices through the WebLN/NWC provider (`window.webln.sendPayment` / direct NWC `pay_invoice`), which gives us per-zap control in the batch loop.
|
||
- **Fallback, not a mode:** a recipient LNURL without zap support degrades that single row to a plain payment (labeled). Everything else stays receipt-grade.
|
||
- **Dead-wallet recovery:** if the connected wallet fails to pay (e.g. NWC wallet asleep — `no info event (kind 13194)`), the zap holds its already-fetched invoice and offers **Retry** (resolving the *most recently connected* provider at click time, so a BC reconnect beats a dead extension) or **Pay by QR** — the invoice QR (zap receipt baked in) can be paid by *any* wallet, phone-scan included. Pure client-side, GitHub-Pages friendly.
|
||
- **Invoice validation (LUD-06 + NIP-57):** every invoice is fully decoded (`zap-core.js`, unit-tested in `test/zap-core.test.js`) before payment — bech32 checksum, mainnet-only hrp, parsed amount **must equal the requested msat** (hard fail; blocks a malicious callback returning a higher-value invoice), plus expiry. For zaps, the description hash is checked against the **serialized zap-request event** (the exact string sent to the callback): bound → UI claims a verified receipt; unbound (e.g. primal embeds a constant hash, minibits omits it) → payment proceeds but the UI honestly says "receipt unverified" / "no zap receipt" and never claims baking. rizful (Zapstore) binds correctly and is reported as verified. Untrusted invoices are never interpolated into HTML — assigned via DOM properties.
|
||
- **Payment-failure triage:** wallet rejections are classified — transient connection failures (`13194`, timeouts, "not connected") get the recovery panel (Retry with the most-recently-connected provider / Pay by QR); cancellations and terminal errors (insufficient balance, invalid invoice) go through the ordinary error toast with the provider's message. One recovery at a time: a second failed zap can't destroy the pending invoice's controls (blocked with a hint + dismissible via ✕).
|
||
|
||
## 6. Trust & moderation
|
||
|
||
- Zap addresses go through the same PR review as everything else; the curator pays extra attention to *changes* to existing addresses (impersonation vector: fake "Amethyst" with attacker's address).
|
||
- Nice-to-have badge: "verified" when the address was added by a PR from the project's own domain/maintainer — can come later, review is enough for v1.
|
||
- The curator default-zap is clearly labeled ("nostr.net — curator & host") and can be unchecked. Transparent, not sneaky.
|
||
- NIP-57 receipts double as **public auditability**: anyone can verify on Zaplife/relays which projects actually receive zaps — hard to run a silent skim.
|
||
|
||
## 7. Plan (deliberately vibe-codeable)
|
||
|
||
| Phase | Scope | Effort |
|
||
|---|---|---|
|
||
| **0** | Markdown convention + contributing docs + PR template + seed ~15–25 flagship entries with known-good addresses (via PRs/issues to owners) | ~½ day (mostly outreach) |
|
||
| **1** | Parser + zap/donate chips on cards + nav "Fund nostr.net" button | ~½ day |
|
||
| **2** | `/grant` page: checkboxes, presets, amount, Bitcoin Connect, sequential NIP-57 zap loop (zap requests + receipts, anonymous fallback), progress/retry/summary, share button | ~1.5–2 days |
|
||
| **3** | per-project amounts, verified badge, grantless subdomain alias, "Zap Day" event mode | later |
|
||
|
||
**"Live by Thursday" scope = Phases 0–2** (~2.5–3 days total — zap receipts are merged into launch scope, not deferred). Even Phase 1 alone is shillable on the podcast ("every project on nostr.net is now zappable, send a PR to add yours").
|
||
|
||
## 8. Open questions
|
||
|
||
1. ~~**Curator lightning address**~~ **Resolved:** `aljaz@minibits.cash` — tip jar for the relay/curation work, pinned + pre-checked on `/grant`.
|
||
2. **Subdomain or path?** `nostr.net/grant` is zero-infra; `grantless.nostr.net` is a better story on a podcast but GitHub Pages serves one custom domain per site → likely a redirect rule or tiny separate repo. Recommend: launch on `/grant`, alias later if the name sticks.
|
||
3. **Bootstrap addresses** — **Resolved for launch: 31 projects seeded** (see appendix). Confirmed so far via owner-PR: **Hivetalk → `bitkarrot@primal.net`**. The rest were harvested from projects' own sources (repo donation sections, official npub profiles, homepage links) and live-verified — see appendix. Still missing flagship addresses for: snort, Primal, Amethyst, YakiHonne, Iris, Habla, highlighter, zap.stream (owners should PR them).
|
||
4. **Sender identity default** — remember "identified via NIP-07" vs "anonymous" per visit (localStorage), or ask each batch?
|
||
5. **Batch amount defaults** — flat 1000 sats per project, or let each entry advertise a suggested amount in markdown?
|
||
|
||
---
|
||
|
||
## Appendix: seeded zap addresses & provenance
|
||
|
||
All addresses below are **live-verified** (lnurlp endpoint responds, zap-receipt support noted) and sourced from the project itself. Sources: `repo` = donation section in the project's own GitHub README · `npub` = lud16 on the project's official nostr profile (npub resolved from the project's own domain via `.well-known/nostr.json` or homepage) · `site` = project's own website.
|
||
|
||
| Project | Address | Source | NIP-57 receipts |
|
||
|---|---|---|---|
|
||
| nostr.net (curator) | aljaz@minibits.cash | owner | ✅ |
|
||
| Hivetalk | bitkarrot@primal.net | owner PR | ✅ |
|
||
| Damus | damus@sendsats.lol | npub + site (damus.io lightning: link → sendsats.lol/.well-known/lnurlp/damus) | ✅ |
|
||
| Coracle | hodlbod@getalby.com | npub (coracle.social) | ✅ |
|
||
| Zapstore | zapstore@rizful.com | npub (zapstore.dev) | ✅ |
|
||
| Nos | strongsnail1@primal.net | npub (nos.social) | ✅ |
|
||
| noStrudel | nostrudel@npub.cash | npub (nostrudel.ninja) | ✅ |
|
||
| Nostur | weathereddarkness25@getalby.com | npub (nostur.com) | ✅ |
|
||
| Jumble | codytseng@getalby.com | repo | ✅ |
|
||
| Vega | jure@getalby.com | repo | ✅ |
|
||
| Mostro | mostro_p2p@sats.mobi | npub (mostro.network) | ✅ |
|
||
| nostrich.love | nostrich@wallet.yakihonne.com | npub | ✅ |
|
||
| Decent Newsroom | decentnewsroom@rizful.com | npub | ✅ |
|
||
| Nuxstr | sebastian@lnd.sebastix.com | repo | ✅ |
|
||
| Fenrir-s | parkinghot99@walletofsatoshi.com | repo | ✅ |
|
||
| nostr-filter-relay | rifat@getalby.com | repo | ✅ |
|
||
| hunos.hu | hunosrelay@minibits.cash | npub | ✅ |
|
||
| nostress | hberaud@nostrcheck.me | repo | ✅ (redirects → primal) |
|
||
| noscrypt | chiptuner@coinos.io | repo | ✅ |
|
||
| nostr-hooks | sepehr@getalby.com | repo | ✅ |
|
||
| persian nostr book | kehiy@walletofsatoshi.com | repo | ✅ |
|
||
| NostrComments | slurpnc@coinos.io | repo | ✅ |
|
||
| cafe-society.news | cafe@getalby.com | repo | ✅ |
|
||
| Obsidian Nostr Writer | magoo@getalby.com | repo | ✅ |
|
||
| toll-booth | profusemeat89@walletofsatoshi.com | repo | ✅ |
|
||
| nostrcheck.me | public@nostrcheck.me | npub | ✅ (redirects → walletofsatoshi) |
|
||
| nostr.build | nostrbob@primal.net | npub | ✅ |
|
||
| NostrMedia.com | b0cc79@wallet.yakihonne.com | npub | ✅ |
|
||
| Zap Cooking | zapcooking@sats.zap.cooking | npub | ✅ |
|
||
| Zaplife | pablof7z@primal.net | npub | ✅ |
|
||
| Plebstr | passivebit94@walletofsatoshi.com | npub | ✅ |
|
||
| ZeusLN | tips@pay.zeusln.app | repo (BTCPay lnurl) | ❌ plain pay only |
|
||
|
||
**Found but dead** (address published by project but endpoint down — not added): LifPay hello@lifpay.me · Flamingo t4t5@t4t5.com · Openvibe matej@openvibe.social · nostr-fetch jiftechnify@eclair.c-stellar.net · Nostros nostros@getalby.com.
|
||
|
||
**Skipped** (example/placeholder addresses in repos, wrong owner, or project not on the list): nostr-to-rss, mostro-cli, gitnostr fork (address belongs to upstream author), Fountain app (not listed), aka-extension (not listed).
|