Replaces every kind 30223 surface with kind 33863 -- the self-authored fundraising campaign with a single `w` Bitcoin wallet endpoint. Hard cutoff: no migration, no dual-read, no legacy support. Schema (src/lib/campaign.ts): - `CAMPAIGN_KIND` constant bumped 30223 -> 33863. - New `CampaignWallet` type with `onchain` (`bc1q`/`bc1p`) and `sp` (`sp1`) modes, prefix-disambiguated. Bitcoin-mainnet only; testnet/regtest/lightning prefixes are rejected at parse time. - New `parseCampaignWallet()` validates bech32 via bitcoinjs-lib for on-chain addresses and shape-checks silent-payment codes. - `ParsedCampaign` drops `recipients`, `category`, `tags`, `location`, `archived`, `image` (-> `banner`), `goalSats` (-> `goalUsd`). Adds `wallet` and `bannerImeta` (parsed NIP-92). - `CAMPAIGN_CATEGORIES`, `CampaignCategory`, `LEGACY_CAMPAIGN_CATEGORY_ALIASES`, `getCampaignPrimaryTagLabel`, `splitDonation`, `minDonationForSplit`, `DonationSplit`, `CampaignRecipient` removed. Publishing (useDonateCampaign): - Single-output PSBT paying `campaign.wallet.value`. - Kind 8333 receipt has NO `p` tags -- campaigns are not Nostr-identity recipients. `i`, `amount`, `a`, `K`, `alt` only. - SP campaigns are refused with a clear error directing donors to external BIP-352-capable wallets via the on-page QR/copy panel. Verification (useOnchainZaps.verifyOnchainZap): - Two modes: identity-recipient (existing `p`-tag derivation) and campaign-wallet (match outputs against `campaign.w`). The branch is selected by whether the receipt has an `a` tag pointing at a kind 33863 campaign. SP-targeted receipts are rejected. Querying (useCampaigns, useAllCampaigns, useCampaignDonations): - Drop `category`, `recipientPubkeys`, `includeArchived` options. - `useCampaignDonations` now takes a `ParsedCampaign` and verifies every receipt on-chain against the campaign's `w` address before counting it. SP campaigns short-circuit to zeros. - Search drops `location` and `t`-tag branches; title/summary/story only. UI: - `CampaignCard`, `HeroCampaignSpotlight`, `CampaignsPage`: drop recipient counts, archived/category badges, `location`; use `banner`. Silent-payment campaigns render a "Private -- totals not public" notice instead of progress. - `CampaignDetailPage`: archive flow replaced with NIP-09 kind 5 deletion. Drops the multi-beneficiary recipient column. Donate column shows the in-app PSBT donate button (on-chain) plus the always- available external-wallet QR/copy panel. SP campaigns show the panel only -- no in-app donate. - `CreateCampaignPage`: drops Beneficiaries section, tag input, and USD-to-sats conversion. Adds a Wallet field with mode-aware validation hint. Goal is integer USD. Banner upload captures NIP-94 tags and converts to NIP-92 `imeta` at publish. - `DonateDialog`: collapses ~1200 LOC of split logic into a single- output flow. Form -> Confirm -> Success. Logged-out and signer- unsupported users are pointed at the external-wallet panel. - New `CampaignWalletDonatePanel` component (replaces the pubkey-derived `BeneficiaryDonateDialog`). QR + copy + open-in-wallet for any `bc1`/`sp1` endpoint, with mode-appropriate privacy notice. Removed: - `useArchiveCampaign` hook (closure via NIP-09 deletion only). - `ClaimPage` and its `/claim` route (claim-for-someone-else flow no longer applies -- campaigns are self-authored). - `BeneficiaryDonateDialog.tsx` (replaced by `CampaignWalletDonatePanel.tsx`). - Community-donate synthesis hack in `CommunityDetailPage` (no more fabricating a `ParsedCampaign` from community moderators). NIP.md was updated separately to specify Kind 33863.
Agora
Power to the people.
Agora is a Nostr client focused on community ownership, expressive identity, and censorship resistance. This repository (agora-3) is the Agora-branded app built from the Ditto codebase.
What This Repo Is
- Agora product identity (name, theme, assets, native IDs)
- Ditto-derived implementation with broad Nostr feature coverage
- Configurable deployment defaults via
agora.json
Features
- Community-first social client: notes, articles, comments, reposts, reactions, and rich event rendering
- Theming system: built-in presets + custom color/font/background themes that can be shared as events
- Lightning support: zaps with Nostr Wallet Connect and WebLN
- Private messaging: NIP-04 and NIP-17 direct messages
- Mobile app shell: Capacitor-powered Android/iOS wrappers
- Self-hostable: static web build + configurable relay and upload infrastructure
Getting Started
Prerequisites
- Node.js 22+
- npm 10.9.4+
Development
git clone https://gitlab.com/soapbox-pub/agora-3.git
cd agora-3
npm install
npm run dev
Development server: http://localhost:8080
Docker Getting Started
Use Docker Compose when you want the nginx reverse-proxy stack (necessary if you want decryptable media in messages - kind 15s of NIP 17):
git clone https://gitlab.com/soapbox-pub/agora-3.git
cd agora-3
cp .env.example .env
docker compose up --build
Proxy URL: http://localhost:8083
This starts:
viteservice on the internal Docker network (vite:8080)webservice (nginx) on host port8082, proxying to Vite with websocket support
Stop stack:
docker compose down
Production-style container build:
docker compose -f docker-compose.prod.yml up --build
Build
npm run build
Build output: dist/
Validate
npm test
This runs type-checking, linting, unit tests, and production build checks.
Configuration
Build-time config is read from agora.json (gitignored by default so each deployment can provide its own values).
{
"theme": "dark",
"relayMetadata": {
"relays": [
{ "url": "wss://relay.ditto.pub", "read": true, "write": true },
{ "url": "wss://relay.primal.net", "read": true, "write": true },
{ "url": "wss://relay.damus.io", "read": true, "write": true }
]
},
"blossomServers": [
"https://blossom.ditto.pub",
"https://blossom.primal.net/"
]
}
Configuration priority (highest first):
- User settings (local storage)
- Build config (
agora.json) - Hardcoded app defaults
Use a custom config path:
CONFIG_FILE=./my-config.json npm run build
Deployment
Agora builds to static files and can be deployed to any static host.
- GitLab/GitHub Pages
- Netlify/Vercel
- VPS or any web server with SPA routing fallback
For Android:
npm run build
npx cap sync
npx cap open android
Tech Stack
| Layer | Technology |
|---|---|
| Framework | React 18 |
| Build | Vite |
| Language | TypeScript |
| Styling | TailwindCSS 3 + shadcn/ui |
| Routing | React Router |
| Data | TanStack Query |
| Nostr | Nostrify + nostr-tools |
| Mobile | Capacitor |
| Testing | Vitest + React Testing Library |
Contributing
Read CONTRIBUTING.md before opening a merge request.