create-fedi-app 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +2 -0
- package/dist/index.js +11113 -0
- package/dist/templates/base/.env.example +5 -0
- package/dist/templates/base/app/demo/page.tsx +25 -0
- package/dist/templates/base/app/globals.css +95 -0
- package/dist/templates/base/app/layout.tsx +39 -0
- package/dist/templates/base/app/page.tsx +83 -0
- package/dist/templates/base/components/FediDevToolbar/FediDevToolbar.tsx +170 -0
- package/dist/templates/base/components/providers.tsx +63 -0
- package/dist/templates/base/env.ts +10 -0
- package/dist/templates/base/hooks/useFediInternal.ts +41 -0
- package/dist/templates/base/lib/fedi-types.ts +96 -0
- package/dist/templates/base/lib/fedi.ts +18 -0
- package/dist/templates/base/lib/nostr/hooks.ts +52 -0
- package/dist/templates/base/lib/nostr/index.ts +9 -0
- package/dist/templates/base/lib/nostr/mock.ts +60 -0
- package/dist/templates/base/lib/nostr/provider.tsx +64 -0
- package/dist/templates/base/lib/utils.ts +3 -0
- package/dist/templates/base/lib/webln/hooks.ts +67 -0
- package/dist/templates/base/lib/webln/index.ts +12 -0
- package/dist/templates/base/lib/webln/mock.ts +96 -0
- package/dist/templates/base/lib/webln/provider.tsx +52 -0
- package/dist/templates/base/next.config.ts +3 -0
- package/dist/templates/base/package.json +40 -0
- package/dist/templates/base/proxy.ts +8 -0
- package/dist/templates/base/tsconfig.json +20 -0
- package/dist/templates/base/vitest.config.ts +6 -0
- package/dist/templates/base/vitest.setup.ts +40 -0
- package/dist/templates/modules/ai-assistant/app/api/assistant/route.ts +45 -0
- package/dist/templates/modules/ai-assistant/app/demo/assistant/AssistantDemoClient.tsx +70 -0
- package/dist/templates/modules/ai-assistant/app/demo/assistant/page.tsx +23 -0
- package/dist/templates/modules/ai-assistant/components/ai/Assistant.tsx +220 -0
- package/dist/templates/modules/ai-assistant/components/ai/AssistantProvider.tsx +71 -0
- package/dist/templates/modules/ai-assistant/lib/ai/providers.ts +49 -0
- package/dist/templates/modules/ai-assistant/module.json +48 -0
- package/dist/templates/modules/ai-chat-gated/app/api/chat/invoice/route.ts +15 -0
- package/dist/templates/modules/ai-chat-gated/app/api/chat/route.ts +57 -0
- package/dist/templates/modules/ai-chat-gated/app/demo/ai-chat/page.tsx +58 -0
- package/dist/templates/modules/ai-chat-gated/components/ai/ChatMessage.tsx +50 -0
- package/dist/templates/modules/ai-chat-gated/components/ai/GatedChat.tsx +181 -0
- package/dist/templates/modules/ai-chat-gated/components/ai/PaymentGate.tsx +168 -0
- package/dist/templates/modules/ai-chat-gated/lib/ai/providers.ts +49 -0
- package/dist/templates/modules/ai-chat-gated/lib/chat-payment.ts +161 -0
- package/dist/templates/modules/ai-chat-gated/module.json +62 -0
- package/dist/templates/modules/ai-rules/.cursorrules +8 -0
- package/dist/templates/modules/ai-rules/.github/copilot-instructions.md +8 -0
- package/dist/templates/modules/ai-rules/CLAUDE.md +8 -0
- package/dist/templates/modules/ai-rules/module.json +20 -0
- package/dist/templates/modules/ai-rules/rules/OVERVIEW.md +56 -0
- package/dist/templates/modules/ai-rules/rules/architecture.md +108 -0
- package/dist/templates/modules/ai-rules/rules/design-system.md +94 -0
- package/dist/templates/modules/ai-rules/rules/fedi-api.md +120 -0
- package/dist/templates/modules/ai-rules/rules/nostr.md +232 -0
- package/dist/templates/modules/ai-rules/rules/patterns.md +408 -0
- package/dist/templates/modules/ai-rules/rules/testing.md +238 -0
- package/dist/templates/modules/ai-rules/rules/webln.md +241 -0
- package/dist/templates/modules/database/drizzle/supabase/0000_initial.sql +7 -0
- package/dist/templates/modules/database/drizzle/supabase/meta/_journal.json +13 -0
- package/dist/templates/modules/database/drizzle/turso/0000_initial.sql +7 -0
- package/dist/templates/modules/database/drizzle/turso/meta/_journal.json +13 -0
- package/dist/templates/modules/database/drizzle.config.supabase.ts +10 -0
- package/dist/templates/modules/database/drizzle.config.turso.ts +11 -0
- package/dist/templates/modules/database/env.supabase.ts +24 -0
- package/dist/templates/modules/database/env.turso.ts +23 -0
- package/dist/templates/modules/database/lib/db/index.supabase.ts +19 -0
- package/dist/templates/modules/database/lib/db/index.turso.ts +20 -0
- package/dist/templates/modules/database/lib/db/schema.supabase.ts +13 -0
- package/dist/templates/modules/database/lib/db/schema.turso.ts +13 -0
- package/dist/templates/modules/database/module.json +110 -0
- package/dist/templates/modules/ecash-balance/app/demo/ecash/page.tsx +115 -0
- package/dist/templates/modules/ecash-balance/components/fedi/BalanceDisplay.tsx +162 -0
- package/dist/templates/modules/ecash-balance/components/fedi/FediVersionBadge.tsx +39 -0
- package/dist/templates/modules/ecash-balance/components/fedi/InstallMiniAppButton.tsx +74 -0
- package/dist/templates/modules/ecash-balance/hooks/useFediBalance.ts +65 -0
- package/dist/templates/modules/ecash-balance/module.json +14 -0
- package/dist/templates/modules/lnurl/app/api/lnurlauth/route.ts +118 -0
- package/dist/templates/modules/lnurl/app/api/lnurlp/[username]/route.ts +70 -0
- package/dist/templates/modules/lnurl/app/api/lnurlw/route.ts +57 -0
- package/dist/templates/modules/lnurl/app/demo/lnurl/page.tsx +136 -0
- package/dist/templates/modules/lnurl/components/lnurl/LnurlAuth.tsx +156 -0
- package/dist/templates/modules/lnurl/components/lnurl/LnurlPay.tsx +36 -0
- package/dist/templates/modules/lnurl/components/lnurl/LnurlQR.tsx +96 -0
- package/dist/templates/modules/lnurl/components/lnurl/LnurlWithdraw.tsx +141 -0
- package/dist/templates/modules/lnurl/lib/lnurl-auth-verify.ts +87 -0
- package/dist/templates/modules/lnurl/lib/lnurl-store.ts +112 -0
- package/dist/templates/modules/lnurl/lib/lnurl-utils.ts +56 -0
- package/dist/templates/modules/lnurl/module.json +27 -0
- package/dist/templates/modules/multispend-demo/app/demo/multispend/MultispendDemoClient.tsx +109 -0
- package/dist/templates/modules/multispend-demo/app/demo/multispend/page.tsx +23 -0
- package/dist/templates/modules/multispend-demo/components/multispend/ApprovalVote.tsx +122 -0
- package/dist/templates/modules/multispend-demo/components/multispend/MultispendDemo.tsx +220 -0
- package/dist/templates/modules/multispend-demo/components/multispend/MultispendProposal.tsx +213 -0
- package/dist/templates/modules/multispend-demo/components/multispend/ProposalList.tsx +49 -0
- package/dist/templates/modules/multispend-demo/hooks/useMultispendDemo.ts +127 -0
- package/dist/templates/modules/multispend-demo/lib/multispend-types.ts +33 -0
- package/dist/templates/modules/multispend-demo/lib/multispend-utils.ts +69 -0
- package/dist/templates/modules/multispend-demo/module.json +18 -0
- package/dist/templates/modules/nostr-feed/app/demo/nostr-feed/NostrFeedDemoClient.tsx +134 -0
- package/dist/templates/modules/nostr-feed/app/demo/nostr-feed/page.tsx +23 -0
- package/dist/templates/modules/nostr-feed/components/nostr/NostrFeedProvider.tsx +47 -0
- package/dist/templates/modules/nostr-feed/components/nostr/NoteCard.tsx +68 -0
- package/dist/templates/modules/nostr-feed/components/nostr/NoteFeed.tsx +109 -0
- package/dist/templates/modules/nostr-feed/components/nostr/PublishNote.tsx +104 -0
- package/dist/templates/modules/nostr-feed/components/nostr/ZapButton.tsx +140 -0
- package/dist/templates/modules/nostr-feed/lib/nostr/relay.ts +107 -0
- package/dist/templates/modules/nostr-feed/lib/nostr-zap.ts +159 -0
- package/dist/templates/modules/nostr-feed/module.json +25 -0
- package/dist/templates/modules/nostr-identity/app/demo/nostr/page.tsx +136 -0
- package/dist/templates/modules/nostr-identity/components/nostr/IdentityBadge.tsx +109 -0
- package/dist/templates/modules/nostr-identity/components/nostr/NostrLogin.tsx +107 -0
- package/dist/templates/modules/nostr-identity/components/nostr/SignedMessage.tsx +103 -0
- package/dist/templates/modules/nostr-identity/hooks/useIdentityFlow.ts +61 -0
- package/dist/templates/modules/nostr-identity/lib/nostr-utils.ts +30 -0
- package/dist/templates/modules/nostr-identity/module.json +15 -0
- package/dist/templates/modules/payment-gated-content/app/api/payment-gate/invoice/route.ts +25 -0
- package/dist/templates/modules/payment-gated-content/app/api/payment-gate/verify/route.ts +39 -0
- package/dist/templates/modules/payment-gated-content/app/demo/payment-gated/article/page.tsx +71 -0
- package/dist/templates/modules/payment-gated-content/app/demo/payment-gated/page.tsx +134 -0
- package/dist/templates/modules/payment-gated-content/components/payment-gated/PayGate.tsx +267 -0
- package/dist/templates/modules/payment-gated-content/lib/payment-gate.ts +195 -0
- package/dist/templates/modules/payment-gated-content/lib/payment-store.ts +104 -0
- package/dist/templates/modules/payment-gated-content/module.json +24 -0
- package/dist/templates/modules/payment-gated-content/proxy.ts +27 -0
- package/dist/templates/modules/webln-payments/app/demo/webln/page.tsx +176 -0
- package/dist/templates/modules/webln-payments/components/webln/InvoiceCard.tsx +170 -0
- package/dist/templates/modules/webln-payments/components/webln/PayButton.tsx +92 -0
- package/dist/templates/modules/webln-payments/components/webln/PaymentHistory.tsx +102 -0
- package/dist/templates/modules/webln-payments/hooks/__tests__/usePaymentFlow.test.tsx +182 -0
- package/dist/templates/modules/webln-payments/hooks/usePaymentFlow.ts +100 -0
- package/dist/templates/modules/webln-payments/lib/payment-history.ts +75 -0
- package/dist/templates/modules/webln-payments/module.json +17 -0
- package/dist/templates/modules/webln-payments/tests/e2e/webln-payment.spec.ts +41 -0
- package/package.json +29 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Project Overview
|
|
2
|
+
|
|
3
|
+
## What is Fedi?
|
|
4
|
+
|
|
5
|
+
Fedi is a Bitcoin/Lightning wallet and community app built on the Fedimint protocol (federated eCash). It lets communities hold Bitcoin collectively without a single custodian. Users interact with local "Federations" — groups of guardians who jointly manage funds using threshold cryptography.
|
|
6
|
+
|
|
7
|
+
Fedi ships a built-in browser (WebView) that hosts Mini Apps — small web apps that get direct access to the user's Lightning wallet and Nostr identity through injected JavaScript APIs.
|
|
8
|
+
|
|
9
|
+
## What is this project?
|
|
10
|
+
|
|
11
|
+
**{{PROJECT_NAME}}** is a Fedi Mini App. It runs exclusively inside Fedi's WebView at a URL the user navigates to (or pins as an installed app).
|
|
12
|
+
|
|
13
|
+
This project was scaffolded with [create-fedi-app](https://github.com/fedi/create-fedi-app).
|
|
14
|
+
|
|
15
|
+
## Stack
|
|
16
|
+
|
|
17
|
+
- **Framework:** Next.js 16 App Router (TypeScript)
|
|
18
|
+
- **Styling:** Tailwind CSS v4 with custom design tokens
|
|
19
|
+
- **Fonts:** Bricolage Grotesque (display), DM Sans (body), JetBrains Mono (mono)
|
|
20
|
+
- **Payments:** `@create-fedi-app/webln` — wraps `window.webln`
|
|
21
|
+
- **Identity:** `@create-fedi-app/nostr` — wraps `window.nostr` (NIP-07)
|
|
22
|
+
- **Fedi internals:** `@create-fedi-app/fedi-types` — TypeScript types for all injected APIs
|
|
23
|
+
- **Testing:** Vitest + React Testing Library + Playwright
|
|
24
|
+
|
|
25
|
+
## Selected modules
|
|
26
|
+
|
|
27
|
+
{{SELECTED_MODULES}}
|
|
28
|
+
|
|
29
|
+
## The WebLN/Nostr injection model
|
|
30
|
+
|
|
31
|
+
Fedi injects two globals into every Mini App's WebView:
|
|
32
|
+
|
|
33
|
+
| Global | Purpose |
|
|
34
|
+
|--------|---------|
|
|
35
|
+
| `window.webln` | Lightning wallet — send/receive payments |
|
|
36
|
+
| `window.nostr` | Nostr identity — public key, signing (NIP-07) |
|
|
37
|
+
|
|
38
|
+
**These are never available outside Fedi.** In a regular browser, both are `undefined`. All code must check before using them. See `rules/webln.md` and `rules/nostr.md` for full references.
|
|
39
|
+
|
|
40
|
+
Fedi also injects `window.fediInternal` (optional, versioned) for app-discovery features. See `rules/fedi-api.md`.
|
|
41
|
+
|
|
42
|
+
## Key constraints for AI agents
|
|
43
|
+
|
|
44
|
+
1. **Never install external wallet libraries** (`webln`, `alby`, `lightning-browser-extension`, etc.). Fedi provides the WebLN provider — adding a library creates conflicts.
|
|
45
|
+
2. **Never install NIP-07 browser extension adapters.** `window.nostr` is already there.
|
|
46
|
+
3. **Always guard injected APIs with `typeof window.X !== 'undefined'`** before calling them — SSR and non-Fedi browsers will not have them.
|
|
47
|
+
4. **This is an App Router project** — components that use browser APIs or React hooks must have `'use client'` at the top.
|
|
48
|
+
5. **Do not add `window.webln.enable()` calls** — the `@create-fedi-app/webln` provider handles this automatically.
|
|
49
|
+
|
|
50
|
+
## Links
|
|
51
|
+
|
|
52
|
+
- Fedi: https://www.fedi.xyz
|
|
53
|
+
- Fedimint protocol: https://fedimint.org
|
|
54
|
+
- WebLN spec: https://webln.dev
|
|
55
|
+
- NIP-07 (Nostr browser extension): https://github.com/nostr-protocol/nips/blob/master/07.md
|
|
56
|
+
- create-fedi-app docs: `.ai/rules/` (this directory)
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# Architecture Reference
|
|
2
|
+
|
|
3
|
+
## File structure
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
{{PROJECT_NAME}}/
|
|
7
|
+
├── app/ # Next.js App Router
|
|
8
|
+
│ ├── layout.tsx # Root layout — fonts, providers, dev toolbar
|
|
9
|
+
│ ├── page.tsx # Home page (client component)
|
|
10
|
+
│ ├── globals.css # Design tokens + Tailwind theme
|
|
11
|
+
│ └── demo/ # Demo pages (remove or repurpose)
|
|
12
|
+
│ ├── page.tsx # Demo index
|
|
13
|
+
│ ├── webln/page.tsx # WebLN payment demo
|
|
14
|
+
│ └── nostr/page.tsx # Nostr identity demo
|
|
15
|
+
├── components/
|
|
16
|
+
│ ├── providers.tsx # WebLNProvider + NostrProvider tree
|
|
17
|
+
│ ├── FediDevToolbar/ # Dev-only mock controls (strips in production)
|
|
18
|
+
│ ├── webln/ # WebLN UI components
|
|
19
|
+
│ └── nostr/ # Nostr UI components
|
|
20
|
+
├── hooks/
|
|
21
|
+
│ └── useFediInternal.ts # window.fediInternal hook
|
|
22
|
+
├── lib/
|
|
23
|
+
│ ├── fedi.ts # isInFedi(), getFediInternalVersion(), formatSats(), shortenNpub()
|
|
24
|
+
│ └── utils.ts # cn() — Tailwind class merging
|
|
25
|
+
├── env.ts # Type-safe env vars via @t3-oss/env-nextjs
|
|
26
|
+
├── next.config.ts
|
|
27
|
+
├── tsconfig.json
|
|
28
|
+
├── vitest.config.ts
|
|
29
|
+
└── vitest.setup.ts
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## App Router conventions
|
|
33
|
+
|
|
34
|
+
This project uses Next.js 16 App Router. Key rules:
|
|
35
|
+
|
|
36
|
+
**Server vs client components**
|
|
37
|
+
|
|
38
|
+
- All components default to Server Components.
|
|
39
|
+
- Add `'use client'` to any component that: uses hooks (`useState`, `useEffect`, etc.), references browser globals (`window`, `document`, `navigator`), or imports from `@create-fedi-app/webln` / `@create-fedi-app/nostr`.
|
|
40
|
+
- The root layout (`app/layout.tsx`) is a Server Component — it wraps children with `<Providers>` which is a client component.
|
|
41
|
+
|
|
42
|
+
**Route structure**
|
|
43
|
+
|
|
44
|
+
- `app/page.tsx` — home (`/`)
|
|
45
|
+
- `app/demo/page.tsx` — demo index (`/demo`)
|
|
46
|
+
- `app/demo/[feature]/page.tsx` — feature demos (`/demo/webln`, `/demo/nostr`)
|
|
47
|
+
- `app/api/[route]/route.ts` — API route handlers
|
|
48
|
+
|
|
49
|
+
**File colocation**
|
|
50
|
+
|
|
51
|
+
Components used only by a single route can live adjacent to that route. Shared components belong in `components/`.
|
|
52
|
+
|
|
53
|
+
## Provider tree
|
|
54
|
+
|
|
55
|
+
Defined in `components/providers.tsx`:
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
<WebLNProvider>
|
|
59
|
+
<NostrProvider>
|
|
60
|
+
{children}
|
|
61
|
+
</NostrProvider>
|
|
62
|
+
</WebLNProvider>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Both providers auto-detect `window.webln` / `window.nostr` on mount and make them available via context. The `FediDevToolbar` (dev-only) injects `MockWebLNProvider` and `MockNostrProvider` to simulate Fedi outside the app.
|
|
66
|
+
|
|
67
|
+
## Module structure
|
|
68
|
+
|
|
69
|
+
Additional features are added as modules. Each module contributes:
|
|
70
|
+
|
|
71
|
+
- **`app/demo/[feature]/`** — demo page for the feature
|
|
72
|
+
- **`components/[feature]/`** — reusable UI components
|
|
73
|
+
- **`hooks/`** — business-logic hooks (e.g. `usePaymentFlow`, `useIdentityFlow`)
|
|
74
|
+
- **Module-specific packages** — `@create-fedi-app/webln`, `@create-fedi-app/nostr`
|
|
75
|
+
|
|
76
|
+
## Where each type of code lives
|
|
77
|
+
|
|
78
|
+
| What | Where |
|
|
79
|
+
|------|-------|
|
|
80
|
+
| Design tokens | `app/globals.css` |
|
|
81
|
+
| Env variable validation | `env.ts` |
|
|
82
|
+
| Fedi utility functions | `lib/fedi.ts` |
|
|
83
|
+
| Class merging helper | `lib/utils.ts` |
|
|
84
|
+
| Browser API hooks | `hooks/` |
|
|
85
|
+
| Feature UI | `components/[feature]/` |
|
|
86
|
+
| Route pages | `app/` |
|
|
87
|
+
| API handlers | `app/api/[name]/route.ts` |
|
|
88
|
+
| Tests | `__tests__/` adjacent to what they test, or `*.test.ts` colocated |
|
|
89
|
+
|
|
90
|
+
## Key packages
|
|
91
|
+
|
|
92
|
+
| Package | Purpose |
|
|
93
|
+
|---------|---------|
|
|
94
|
+
| `@create-fedi-app/webln` | React context + hooks for `window.webln` |
|
|
95
|
+
| `@create-fedi-app/nostr` | React context + hooks for `window.nostr` |
|
|
96
|
+
| `@create-fedi-app/fedi-types` | TypeScript types for all Fedi-injected APIs |
|
|
97
|
+
| `@create-fedi-app/ui` | Shared UI components (added in a later scaffold phase) |
|
|
98
|
+
|
|
99
|
+
## Environment variables
|
|
100
|
+
|
|
101
|
+
Validated at build time in `env.ts` using `@t3-oss/env-nextjs`. Add new variables there, not in raw `process.env` calls. Client-side variables must be prefixed `NEXT_PUBLIC_`.
|
|
102
|
+
|
|
103
|
+
## Routing conventions
|
|
104
|
+
|
|
105
|
+
- **App Router only** — no Pages Router files.
|
|
106
|
+
- **No `index.tsx` files in `app/`** — use `page.tsx` as required by Next.js.
|
|
107
|
+
- **Parallel routes** (`@slot/`) are not used in the base template — add only if needed.
|
|
108
|
+
- **Loading states** use `loading.tsx` files next to `page.tsx` — not custom suspense boundaries unless needed.
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Design System Reference
|
|
2
|
+
|
|
3
|
+
## CSS variables (design tokens)
|
|
4
|
+
|
|
5
|
+
All tokens are defined in `app/globals.css` inside a `@theme {}` block (Tailwind v4 syntax). Use these variables in inline styles and `var()` calls.
|
|
6
|
+
|
|
7
|
+
### Colors
|
|
8
|
+
|
|
9
|
+
```css
|
|
10
|
+
--color-bg: #0A0A0A /* page background */
|
|
11
|
+
--color-surface: #141414 /* card / panel background */
|
|
12
|
+
--color-surface-2: #1C1C1C /* elevated surface (tooltips, popovers) */
|
|
13
|
+
--color-border: rgba(255, 255, 255, 0.08) /* subtle dividers */
|
|
14
|
+
--color-accent: #FF6B35 /* primary action color — Fedi orange */
|
|
15
|
+
--color-accent-dim: rgba(255, 107, 53, 0.15) /* accent tint for backgrounds */
|
|
16
|
+
--color-text: #F0EEE9 /* primary text */
|
|
17
|
+
--color-text-muted: #8A8880 /* secondary / supporting text */
|
|
18
|
+
--color-text-subtle: #4A4845 /* placeholder, disabled text */
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### Typography
|
|
22
|
+
|
|
23
|
+
```css
|
|
24
|
+
--font-display: 'Bricolage Grotesque', system-ui, sans-serif /* headings */
|
|
25
|
+
--font-body: 'DM Sans', system-ui, sans-serif /* body text */
|
|
26
|
+
--font-mono: 'JetBrains Mono', monospace /* code, addresses */
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Border radius
|
|
30
|
+
|
|
31
|
+
```css
|
|
32
|
+
--radius-sm: 4px /* small chips, badges */
|
|
33
|
+
--radius-md: 8px /* buttons, inputs, cards */
|
|
34
|
+
--radius-lg: 12px /* large cards, modals */
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Using tokens in components
|
|
38
|
+
|
|
39
|
+
### Inline styles (when Tailwind class doesn't exist)
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
<div style={{ background: 'var(--color-surface)', borderRadius: 'var(--radius-md)' }}>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Tailwind arbitrary values
|
|
46
|
+
|
|
47
|
+
```tsx
|
|
48
|
+
<div className="bg-[var(--color-surface)] rounded-[var(--radius-md)]">
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Tailwind theme integration
|
|
52
|
+
|
|
53
|
+
The Tailwind theme maps `--color-*` tokens, so you can use shorthand:
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
<div className="bg-surface text-text-muted border border-border">
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Components from @create-fedi-app/ui
|
|
60
|
+
|
|
61
|
+
> Note: UI components are added in the next scaffold phase. Check `packages/ui/src/index.ts` for the current export list.
|
|
62
|
+
|
|
63
|
+
## shadcn/ui conventions
|
|
64
|
+
|
|
65
|
+
This project uses shadcn/ui naming conventions for any added components:
|
|
66
|
+
|
|
67
|
+
- Components live in `components/ui/` (shadcn primitives) and `components/` (app-specific)
|
|
68
|
+
- File names are kebab-case: `components/ui/button.tsx`, `components/ui/card.tsx`
|
|
69
|
+
- Exports are named (not default): `export function Button(...)`
|
|
70
|
+
- Compose with `cn()` from `lib/utils.ts` for conditional classes
|
|
71
|
+
|
|
72
|
+
## Anti-slop rules for AI agents
|
|
73
|
+
|
|
74
|
+
These rules prevent the most common AI-generated UI mistakes in this codebase. Follow them strictly.
|
|
75
|
+
|
|
76
|
+
1. **Use design tokens, not hardcoded hex.** Never write `color: '#FF6B35'` — write `color: 'var(--color-accent)'`. Never write `background: '#141414'` — write `background: 'var(--color-surface)'`. The only exception is truly one-off values with no token equivalent.
|
|
77
|
+
|
|
78
|
+
2. **Never invent new colors.** If a color doesn't exist as a token, use the closest existing token or ask. Do not introduce new hex values, `hsl()`, `rgb()`, or opacity tricks that approximate an existing token.
|
|
79
|
+
|
|
80
|
+
3. **Respect the radius scale.** Use `--radius-sm`, `--radius-md`, or `--radius-lg`. Do not use arbitrary values like `rounded-2xl`, `rounded-full` on rectangles, or `border-radius: 16px`. Avatars and circular elements may use `rounded-full`.
|
|
81
|
+
|
|
82
|
+
4. **Never add box shadows.** The design system is shadowless. Do not add `shadow-*` classes or `box-shadow` styles. Use borders with `--color-border` for separation.
|
|
83
|
+
|
|
84
|
+
5. **Match the font stack.** Headings use `--font-display`. Body copy uses `--font-body` (set on `html`, inherited). Code, addresses, keys, and amounts use `--font-mono`. Do not mix them or introduce new fonts.
|
|
85
|
+
|
|
86
|
+
6. **Don't add color to text for decoration.** Color communicates state: `--color-accent` means "interactive/active", `--color-text-muted` means "supporting info", `--color-text-subtle` means "disabled/placeholder". Don't use accent color on static descriptive text.
|
|
87
|
+
|
|
88
|
+
7. **Minimum touch target is 44×44px.** Any interactive element (button, link, toggle) must have at least `min-h-[44px] min-w-[44px]` or equivalent padding. Never make a button that's 24×24.
|
|
89
|
+
|
|
90
|
+
8. **Loading states must show something.** When `isPaying`, `isLoading`, or `isConnecting` is true, the button/area must visibly change — spinner, opacity reduction, or label change. Never leave the UI frozen without feedback.
|
|
91
|
+
|
|
92
|
+
9. **Errors must surface to the user.** When `paymentError`, `signError`, or any error state is non-null, show the message. Do not silently swallow errors with empty `catch {}` blocks or conditional rendering that hides the error element.
|
|
93
|
+
|
|
94
|
+
10. **No placeholder text in production UI.** Don't generate components with "Lorem ipsum", "TODO", "Sample text", or generic copy. Write real copy, or use a `{{PLACEHOLDER}}` template variable that the dev must fill in.
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# Fedi Internal API Reference
|
|
2
|
+
|
|
3
|
+
`window.fediInternal` is an optional, versioned API injected by Fedi for Mini App discovery features. Unlike `window.webln` and `window.nostr`, it may not be present even inside Fedi (older app versions will not have it).
|
|
4
|
+
|
|
5
|
+
## Version detection
|
|
6
|
+
|
|
7
|
+
Always check both existence and version before calling methods:
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { getFediInternalVersion } from '../lib/fedi';
|
|
11
|
+
|
|
12
|
+
const version = getFediInternalVersion();
|
|
13
|
+
// → 0 | 1 | 2 | null
|
|
14
|
+
|
|
15
|
+
if (version === 2) {
|
|
16
|
+
// v2 methods available
|
|
17
|
+
} else if (version === null) {
|
|
18
|
+
// not in Fedi, or very old version
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
The helper in `lib/fedi.ts`:
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
export function getFediInternalVersion(): 0 | 1 | 2 | null {
|
|
26
|
+
if (typeof window === 'undefined' || !window.fediInternal) return null;
|
|
27
|
+
return window.fediInternal.version as 0 | 1 | 2;
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## TypeScript types
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
type FediInternalV0 = { version: 0 };
|
|
35
|
+
type FediInternalV1 = { version: 1 };
|
|
36
|
+
type FediInternalV2 = {
|
|
37
|
+
version: 2;
|
|
38
|
+
getInstalledMiniApps(): Promise<Array<{ url: string }>>;
|
|
39
|
+
installMiniApp(miniApp: {
|
|
40
|
+
id: string;
|
|
41
|
+
title: string;
|
|
42
|
+
url: string;
|
|
43
|
+
imageUrl?: string | null;
|
|
44
|
+
description?: string;
|
|
45
|
+
}): Promise<void>;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
type FediInternal = FediInternalV0 | FediInternalV1 | FediInternalV2;
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## v2 API
|
|
52
|
+
|
|
53
|
+
### getInstalledMiniApps()
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
const apps = await window.fediInternal.getInstalledMiniApps(): Promise<Array<{ url: string }>>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Returns the list of Mini App URLs the current user has installed in Fedi. Useful for detecting if a companion app is installed, or for showing an "install" prompt.
|
|
60
|
+
|
|
61
|
+
### installMiniApp()
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
await window.fediInternal.installMiniApp({
|
|
65
|
+
id: string; // unique identifier, e.g. "com.example.myapp"
|
|
66
|
+
title: string; // display name shown in Fedi
|
|
67
|
+
url: string; // the Mini App URL
|
|
68
|
+
imageUrl?: string; // icon URL (optional)
|
|
69
|
+
description?: string;
|
|
70
|
+
}): Promise<void>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Triggers Fedi's native UI to add a Mini App to the user's home screen. Resolves when the user confirms (or rejects) the install prompt.
|
|
74
|
+
|
|
75
|
+
## useFediInternal() hook
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
import { useFediInternal } from '../hooks/useFediInternal';
|
|
79
|
+
|
|
80
|
+
const { isAvailable, version, getInstalledMiniApps, installMiniApp } = useFediInternal();
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
| Field | Type | Description |
|
|
84
|
+
|-------|------|-------------|
|
|
85
|
+
| `isAvailable` | `boolean` | True if `window.fediInternal` is present |
|
|
86
|
+
| `version` | `0 \| 1 \| 2 \| null` | Detected version |
|
|
87
|
+
| `getInstalledMiniApps` | `V2['getInstalledMiniApps'] \| null` | Null if version < 2 |
|
|
88
|
+
| `installMiniApp` | `V2['installMiniApp'] \| null` | Null if version < 2 |
|
|
89
|
+
|
|
90
|
+
Non-v2 methods are `null` — always check before calling:
|
|
91
|
+
|
|
92
|
+
```tsx
|
|
93
|
+
'use client';
|
|
94
|
+
import { useFediInternal } from '../hooks/useFediInternal';
|
|
95
|
+
|
|
96
|
+
export function InstallPrompt() {
|
|
97
|
+
const { installMiniApp } = useFediInternal();
|
|
98
|
+
|
|
99
|
+
if (!installMiniApp) return null;
|
|
100
|
+
|
|
101
|
+
return (
|
|
102
|
+
<button onClick={() => installMiniApp({
|
|
103
|
+
id: 'com.example.myapp',
|
|
104
|
+
title: 'My App',
|
|
105
|
+
url: 'https://myapp.example.com',
|
|
106
|
+
})}>
|
|
107
|
+
Add to Fedi
|
|
108
|
+
</button>
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Version history
|
|
114
|
+
|
|
115
|
+
| Version | Features |
|
|
116
|
+
|---------|----------|
|
|
117
|
+
| `null` | Not in Fedi, or Fedi version predates fediInternal |
|
|
118
|
+
| `0` | Fedi version is aware of the API but exposes nothing |
|
|
119
|
+
| `1` | Reserved |
|
|
120
|
+
| `2` | `getInstalledMiniApps`, `installMiniApp` |
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
# Nostr Reference (NIP-07)
|
|
2
|
+
|
|
3
|
+
Nostr is a decentralized social protocol. Fedi injects `window.nostr` (a NIP-07 provider) into every Mini App's WebView, giving the app access to the user's public key and signing capability without ever exposing the private key.
|
|
4
|
+
|
|
5
|
+
## How Fedi injects window.nostr
|
|
6
|
+
|
|
7
|
+
Like `window.webln`, Fedi's native layer injects a NIP-07-compliant `NostrProvider` before the page loads. The private key stays inside the native app — the Mini App only ever receives signed events or the public key.
|
|
8
|
+
|
|
9
|
+
Outside Fedi, `window.nostr` is `undefined`.
|
|
10
|
+
|
|
11
|
+
## Detection pattern
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
if (typeof window.nostr !== 'undefined') {
|
|
15
|
+
const pubkey = await window.nostr.getPublicKey();
|
|
16
|
+
} else {
|
|
17
|
+
// not in Fedi, or provider not available
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Prefer the `@create-fedi-app/nostr` hooks — they handle this for you.
|
|
22
|
+
|
|
23
|
+
## React hooks (preferred)
|
|
24
|
+
|
|
25
|
+
### useNostr()
|
|
26
|
+
|
|
27
|
+
Low-level access to the provider and connection state.
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
import { useNostr } from '@create-fedi-app/nostr';
|
|
31
|
+
|
|
32
|
+
const { provider, pubkey, npub, isLoading, error, isConnected } = useNostr();
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
| Field | Type | Description |
|
|
36
|
+
|-------|------|-------------|
|
|
37
|
+
| `provider` | `NostrProvider \| null` | Raw NIP-07 provider |
|
|
38
|
+
| `pubkey` | `string \| null` | Hex-encoded 32-byte public key |
|
|
39
|
+
| `npub` | `string \| null` | bech32-encoded public key (`npub1…`) |
|
|
40
|
+
| `isLoading` | `boolean` | True while provider initialises |
|
|
41
|
+
| `error` | `Error \| null` | Initialisation error |
|
|
42
|
+
| `isConnected` | `boolean` | Shorthand for `provider !== null` |
|
|
43
|
+
|
|
44
|
+
### useIdentity()
|
|
45
|
+
|
|
46
|
+
Higher-level hook for the common "connect + sign" flow.
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
import { useIdentity } from '@create-fedi-app/nostr';
|
|
50
|
+
|
|
51
|
+
const {
|
|
52
|
+
pubkey,
|
|
53
|
+
npub,
|
|
54
|
+
displayNpub,
|
|
55
|
+
getPublicKey,
|
|
56
|
+
signEvent,
|
|
57
|
+
isConnecting,
|
|
58
|
+
} = useIdentity();
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
| Field | Type | Description |
|
|
62
|
+
|-------|------|-------------|
|
|
63
|
+
| `pubkey` | `string \| null` | Hex public key (null until connected) |
|
|
64
|
+
| `npub` | `string \| null` | bech32 public key |
|
|
65
|
+
| `displayNpub` | `string \| null` | Truncated: `npub1abc…xyz` |
|
|
66
|
+
| `getPublicKey()` | `() => Promise<string \| null>` | Trigger key retrieval |
|
|
67
|
+
| `signEvent(event)` | `(UnsignedNostrEvent) => Promise<NostrEvent \| null>` | Sign a Nostr event |
|
|
68
|
+
| `isConnecting` | `boolean` | True during `signEvent` |
|
|
69
|
+
|
|
70
|
+
## Full API (window.nostr)
|
|
71
|
+
|
|
72
|
+
### getPublicKey()
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
const pubkey = await window.nostr.getPublicKey(): Promise<string>
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Returns the user's hex-encoded 32-byte Schnorr public key. This is the canonical Nostr identity — a stable, unique identifier per user.
|
|
79
|
+
|
|
80
|
+
**This does not request funds or sign anything.** Safe to call for login flows.
|
|
81
|
+
|
|
82
|
+
### signEvent()
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
const signedEvent = await window.nostr.signEvent(event: UnsignedNostrEvent): Promise<NostrEvent>
|
|
86
|
+
|
|
87
|
+
interface UnsignedNostrEvent {
|
|
88
|
+
pubkey: string; // must match the user's pubkey
|
|
89
|
+
created_at: number; // Unix timestamp
|
|
90
|
+
kind: number; // Nostr event kind
|
|
91
|
+
tags: string[][]; // array of tag arrays
|
|
92
|
+
content: string;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
interface NostrEvent extends UnsignedNostrEvent {
|
|
96
|
+
id: string; // hex-encoded event hash (SHA-256 of canonical serialisation)
|
|
97
|
+
sig: string; // hex-encoded Schnorr signature
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Common kinds: `0` = profile metadata, `1` = short text note, `4` = encrypted DM.
|
|
102
|
+
|
|
103
|
+
### getRelays()
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
const relays = await window.nostr.getRelays(): Promise<Record<string, { read: boolean; write: boolean }>>
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Returns the user's configured relay list. Example:
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
{
|
|
113
|
+
'wss://relay.damus.io': { read: true, write: true },
|
|
114
|
+
'wss://nos.lol': { read: true, write: false },
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### nip04 (encrypted DMs)
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
// Encrypt a message to a recipient's pubkey
|
|
122
|
+
const ciphertext = await window.nostr.nip04.encrypt(recipientPubkey: string, plaintext: string): Promise<string>
|
|
123
|
+
|
|
124
|
+
// Decrypt a message encrypted to your pubkey
|
|
125
|
+
const plaintext = await window.nostr.nip04.decrypt(senderPubkey: string, ciphertext: string): Promise<string>
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
NIP-04 uses ECDH + AES-256-CBC. The ciphertext format is `<base64>?iv=<base64>`.
|
|
129
|
+
|
|
130
|
+
## npub format and bech32 encoding
|
|
131
|
+
|
|
132
|
+
The raw public key is a 32-byte value encoded as 64 hex characters. `npub` is the human-readable bech32 encoding of the same bytes:
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
hex: 79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798
|
|
136
|
+
npub: npub10279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
The `@create-fedi-app/nostr` package converts between formats automatically via `@scure/base`.
|
|
140
|
+
|
|
141
|
+
To display a truncated npub (for avatars, etc.):
|
|
142
|
+
```ts
|
|
143
|
+
const displayNpub = npub.slice(0, 8) + '...' + npub.slice(-4);
|
|
144
|
+
// → "npub1abc...xyz4"
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Nostr as login (no username/password)
|
|
148
|
+
|
|
149
|
+
Because `getPublicKey()` returns a stable identifier, you can use it as a passwordless login:
|
|
150
|
+
|
|
151
|
+
1. Get the pubkey — this is the user's "account"
|
|
152
|
+
2. Store any per-user data keyed on `pubkey`
|
|
153
|
+
3. To authenticate a server request, have the user sign a challenge event (kind: 27235, NIP-98)
|
|
154
|
+
|
|
155
|
+
The user never creates an account — their Fedi identity is their account.
|
|
156
|
+
|
|
157
|
+
## Common patterns
|
|
158
|
+
|
|
159
|
+
### Connect on button press (lazy)
|
|
160
|
+
|
|
161
|
+
```tsx
|
|
162
|
+
'use client';
|
|
163
|
+
import { useIdentity } from '@create-fedi-app/nostr';
|
|
164
|
+
|
|
165
|
+
export function ConnectButton() {
|
|
166
|
+
const { pubkey, displayNpub, getPublicKey, isConnecting } = useIdentity();
|
|
167
|
+
|
|
168
|
+
if (pubkey) {
|
|
169
|
+
return <span className="font-mono text-sm">{displayNpub}</span>;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return (
|
|
173
|
+
<button onClick={getPublicKey} disabled={isConnecting}>
|
|
174
|
+
{isConnecting ? 'Connecting…' : 'Connect with Nostr'}
|
|
175
|
+
</button>
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Sign a message to prove identity
|
|
181
|
+
|
|
182
|
+
```ts
|
|
183
|
+
const event = await signEvent({
|
|
184
|
+
kind: 1,
|
|
185
|
+
content: 'Hello from my mini app',
|
|
186
|
+
tags: [],
|
|
187
|
+
created_at: Math.floor(Date.now() / 1000),
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// event.sig proves the user signed this content
|
|
191
|
+
// event.pubkey is their identity
|
|
192
|
+
// event.id is the canonical hash
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## MockNostrProvider (tests)
|
|
196
|
+
|
|
197
|
+
```ts
|
|
198
|
+
import { MockNostrProvider } from '@create-fedi-app/nostr';
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
`MockNostrProvider` uses a deterministic test keypair (well-known secp256k1 generator point) and produces real Schnorr signatures via `@noble/curves`.
|
|
202
|
+
|
|
203
|
+
```ts
|
|
204
|
+
const mock = new MockNostrProvider();
|
|
205
|
+
|
|
206
|
+
await mock.getPublicKey();
|
|
207
|
+
// → '79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'
|
|
208
|
+
|
|
209
|
+
await mock.signEvent({ kind: 1, content: 'test', tags: [], created_at: 0, pubkey: '...' });
|
|
210
|
+
// → fully valid NostrEvent with real id and sig
|
|
211
|
+
|
|
212
|
+
await mock.getRelays();
|
|
213
|
+
// → { 'wss://relay.damus.io': { read: true, write: true }, ... }
|
|
214
|
+
|
|
215
|
+
await mock.nip04.encrypt('somePubkey', 'hello');
|
|
216
|
+
// → base64-encoded fake ciphertext
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
**The test keypair is a well-known vector — NEVER use in production.**
|
|
220
|
+
|
|
221
|
+
Use in Vitest tests by passing to the `NostrProvider` context:
|
|
222
|
+
|
|
223
|
+
```tsx
|
|
224
|
+
import { NostrProvider } from '@create-fedi-app/nostr';
|
|
225
|
+
import { MockNostrProvider } from '@create-fedi-app/nostr';
|
|
226
|
+
|
|
227
|
+
render(
|
|
228
|
+
<NostrProvider mock={new MockNostrProvider()}>
|
|
229
|
+
<YourComponent />
|
|
230
|
+
</NostrProvider>
|
|
231
|
+
);
|
|
232
|
+
```
|