# coinos v3 A self-custodial Bitcoin wallet and Nostr client that runs entirely in the browser. Your keys are made and kept on your device; there is no account, email or password. One app holds an on-chain **Savings** balance, an off-chain **Spending** balance on [Ark](https://ark-protocol.org) with Lightning built in, a `you@coinos.io` payment address, and a full Nostr social client: feeds, zaps, private messages and group chats. - Live at **https://v3.coinos.io** (installable as a PWA) - Android: [Zapstore](https://zapstore.dev), a direct APK at `v3.coinos.io/coinos.apk`, and an F-Droid build (`io.coinos.fdroid`, in review) - Offline: `standalone.html` is the whole wallet in one file you can save and open from disk ## Features ### Wallet core (always included) - **BIP84 / native SegWit** HD wallet from a 12-word BIP39 seed (any valid phrase imports; optional passphrase). - **Watch-only wallets** from an account xpub/zpub: receive and view history without the seed; load the seed later to spend. - **Offline / air-gapped signing**: export a keyless snapshot on an online device, sign on an offline one, and carry the signed transaction back by QR to broadcast. - **Per-wallet networks**: mainnet, testnet, signet, mutinynet or regtest, stamped on each wallet, so wallets on different networks coexist. - **Choose your data source**: Electrum-over-WebSocket servers (`electrum.coinos.io` by default) or Esplora REST explorers, with failover, or your own node. - **Real-time receives** over WebSocket where the source supports it, polling as the fallback; replaced (RBF) transactions are pruned automatically. - **Sending** with coin selection, fee estimation, a fee-rate picker, send-max, multiple recipients and a camera QR scanner (addresses, BIP21, invoices, signed transactions). - **One fresh address at a time**, so scans stay small (no gap-limit probing). - **Fiat** balances and history values in your currency, with the rate at the time of each payment. - 20 languages. ### Spending on Ark (feature: `ark`) A native-JS client for [bark/captaind](https://gitlab.com/ark-bitcoin/bark) Ark servers: gRPC-web and MuSig2 spoken directly, no WASM. coinos runs `ark.coinos.io`; other captaind servers can be chosen. - **Instant off-chain payments** between Ark users, with fees shown before anything moves (free between coinos wallets). - **Lightning in and out of the Ark balance** through the Ark server's own Lightning node (HTLC-locked VTXOs), with no swap service. Routing fees are quoted before you pay. - **Board** on-chain coins into Spending and **offboard** back to Savings or any on-chain address in one cooperative transaction. - **Automatic renewal** before coins expire, plus a server-side scheduled backstop that renews them even while the app is closed. - **Unilateral exit**: publish the coins' pre-signed transaction chain (CPFP-bumped, TRUC-aware) and claim after the timelock with your key alone, even if the server is gone. Progress is shown live. - **Full receive-side validation** of every VTXO's transaction chain, and crash-safe ceremonies that checkpoint around every server round-trip. - Ark state lives in IndexedDB, fronted by memory, and is reconciled against the server before money moves. ### Payment addresses (feature: `names`) - **`you@coinos.io`**: a name claimed on first run that works as a Lightning address (LNURL-pay) and as a **BIP-353** DNS payment address (`₿you@coinos.io`, a DNSSEC-signed TXT record). - Paying a BIP-353 name validates its **DNSSEC chain in the browser**. - **BOLT12 offers**: pay `lno1…` offers (invoice signatures checked client-side) and publish your own offer in your DNS record. - **CLINK** offer codes (`noffer1…`): request invoices over Nostr. - **Moving from coinos.io**: existing coinos.io users can bring their username over; payments to the legacy account are forwarded to the new wallet. ### Zaps and Nostr Wallet Connect (features: `zaps`, `nwc`) - **NIP-57 zaps** to any npub, Lightning address or LNURL, with one-tap zaps in the feed and a custom-amount screen on long press. - **coinos as an NWC wallet service (NIP-47)**: connect Damus, Amethyst, Primal and others, with per-connection keys and budgets. - **Background answering**: a Web Push from `nwcpush` wakes the service worker, which pays and replies from the browser even with the app closed (keys never leave the device). Multi-device safe: one device answers each request. ### Gifts (feature: `gifts`) - **Gift links**: bitcoin you send as a link, claimable straight into a brand-new wallet or an existing one. On-chain gifts use a one-time key so they can't be front-run; Ark gifts are claimed instantly and free. - Reclaim or revoke unclaimed gifts; optionally lock a gift to a Nostr account (the claim code is DM'd to them). ### Point of sale (feature: `pos`) - Enter an amount, the customer picks a tip, and one Lightning invoice settles it; bills and tips are kept apart in history. ### Sign-in (feature: `nostrlogin`) - **Seed phrase**, created or imported. - **Passkeys**: the WebAuthn PRF extension derives the wallet's secret. - **Google**, via pomegranate: the Nostr key is FROST-sharded 2-of-3 across independent operators. - **Nostr signers**: browser extensions (NIP-07), remote signers (`bunker://` and `nostrconnect://`, NIP-46) and Amber on Android (NIP-55). A pasted or signer key becomes a recovery key for the wallet. - Several identities side by side, each with its own wallet; switch or sign out per identity. ### Cross-device sync (feature: `sync`) - Wallet state, read receipts and custom feeds are **NIP-44 encrypted to yourself** and published as replaceable Nostr events, one per domain (core wallet, Ark, NWC, …), so only what changed republishes. - Domains too big for a relay event go to **Blossom** as an encrypted blob (`blossom.coinos.io` by default, or any Blossom server); the event carries its hash. - A durable outbox holds signed snapshots until a relay accepts them. ### Social: a Nostr client (feature: `messages`) **Feeds** - **Following**, a curated **Popular** feed (prose in your language that several people engaged with), and **custom feeds** from people, hashtags, relays, follow packs or NIP-51 lists. Custom feeds sync across devices; people feeds publish as NIP-51 follow sets. - Shareable feed links (`/feed/`, `/feed/t/`, packs and lists). - **Views**: All, an **Images** grid, **Videos** (full-screen vertical clips with autoplay), and long-form **Articles** (NIP-23). - Cached posts paint first and new ones arrive without moving what you're reading; long feeds are windowed. **Posts** - Replies, reposts, quotes, reactions, polls (NIP-88) and zaps on one action bar, with who-reacted panels. - Link previews, YouTube embeds, audio players for podcast links, article cards for `naddr` references, and inline quoted notes. - Compose with pictures and video (compressed on device, uploaded to Blossom media hosts). - Linkable notes and profiles (`/nevent1…`, `/npub1…`), with server-rendered pages for link previews elsewhere. **Moderation** - Your NIP-51 mute list (people, words, hashtags, threads, including its private half) applied everywhere; reports (NIP-56) from people you follow fold posts; duplicate and hashtag-spam filtering. **Messages** - **Private DMs** (NIP-17 gift-wrapped), with replies, reactions and encrypted attachments. - **Communities** on Concord: end-to-end encrypted channels compatible with Vector, including Vector's custom emoji packs (NIP-30) and community icons. Every user joins the coinos community; anyone can create or join others. - **Group chats** on White Noise (Marmot) and on **Cordn** (the MLS group chats in Amethyst 1.17 and the Cordn app, delivered through a coordinator that only ever sees sealed bytes), both on a native-JS MLS (RFC 9420) implementation. - **Notifications** for mentions, replies, reactions and zaps, with push delivery to your devices. ### Extras - **Hats** (feature: `hats`): supporter hats worn above your avatar. ## Modular builds Every optional feature above is a **plugin behind a build seam**. `COINOS_FEATURES` selects what ships; excluded features never enter the bundle (their code, their network endpoints, their crypto): ```bash bun run build # everything COINOS_FEATURES=ark,gifts bun run build # core + ark + gifts bun run build:minimal # core on-chain wallet only ``` Feature names: `gifts`, `ark`, `nostrlogin`, `names`, `zaps`, `nwc`, `hats`, `sync`, `messages`, `pos`. `tools/minimal-smoke.js` checks the minimal profile is a working on-chain wallet with no feature code in the bundle. Other build switches: | Variable | Effect | | --- | --- | | `COINOS_TARGET=staging` | mutinynet-first defaults and a visible staging stamp | | `COINOS_NO_SW=1` | no service worker (the bundled Android/F-Droid build) | ## Develop Requires [Bun](https://bun.sh). ```bash bun install bun run dev # http://localhost:5173 (rebuilds on each refresh) ``` The dev server proxies local regtest backends (Electrum, Esplora) so the app works unchanged from a phone on the LAN. The end-to-end suites in `tools/` drive headless Chrome through real flows (boarding, Ark sends, Lightning, offboard, exit, gifts, feeds, chat) against a regtest stack or live relays. ## Build ```bash bun run build # → dist/ ``` - `dist/index.html` is a small shell; the app loads as `app-.js` plus lazily loaded `chunk-.js` features. - `sw.js`, `manifest.webmanifest` and icons make it an installable PWA that works offline. - `dist/standalone.html` inlines everything (code, libraries, CSS, fonts): save it and open it from disk with no server. ## Services in this repo The app is static; these small services run beside it (Bun): | Directory | Service | | --- | --- | | `names/` | Payment-name registrar (`names.coinos.io`): BIP-353 TXT records via the DNS provider's API, LNURL-pay for `@coinos.io` names, NIP-98 auth, link previews | | `feed/` | Public feed snapshot (`/api/feed`) and server-rendered note pages | | `nwcpush/` | Watches relays for NWC requests and push notifications, and wakes devices with Web Push | | `blossom/` | `blossom.coinos.io`: a Blossom server that only stores encrypted sync envelopes | | `bridge/` | Ark→Lightning swap bridge (retired; the app uses the Ark server's Lightning) | ## How state is kept 1. **sessionStorage** keeps the wallet open across a refresh (cleared on lock or tab close). 2. **localStorage** caches scanned state, feeds and profiles per wallet and network, so a reload paints instantly. 3. **IndexedDB** holds Ark coins, the feed media index and White Noise MLS state. 4. **Nostr** (and Blossom for large domains) holds the encrypted cross-device copy. ## Layout | Path | Purpose | | --- | --- | | `src/wallet.js` | BIP84 core: derivation, scanning, coin selection, signing, realtime, cache, and the feature seams | | `src/api.js` / `src/electrum.js` | Data sources: Esplora REST and Electrum-over-WebSocket, presets and failover | | `src/ark/` | Native-JS Ark: gRPC-web codec, arkoor sends, board, refresh rounds, offboard, Lightning, exit, validation, crash-safe manager, IndexedDB store | | `src/features/` | Feature plugins (see Modular builds); `index.js` is rewritten at build time by `COINOS_FEATURES` | | `src/nostr.js` / `src/sync-*.js` | Encrypted state sync, relay pool, Blossom envelopes, the sync outbox | | `src/nostr-login.js` / `src/passkey.js` / `src/pomegranate.js` | Nostr signers, passkey PRF sign-in, Google via FROST | | `src/dm.js` / `src/concord.js` / `src/mls.js` / `src/marmot*.js` / `src/cordn*.js` / `src/contextvm.js` | NIP-17 DMs, Concord communities, MLS, the White Noise layer, and Cordn over ContextVM | | `src/bip353.js` / `src/dnssec.js` / `src/bolt12.js` / `src/noffer.js` / `src/clink.js` / `src/lnurl.js` | Payment-name resolution with DNSSEC validation, BOLT12, CLINK, LNURL and zaps | | `src/sw-nwc.js` / `src/nwc-*.js` | Background NWC answering in the service worker | | `src/app.js` | UI controller (vanilla DOM, no framework) and feature registry | | `build.js` / `dev.js` | Bun bundler (split build, PWA, standalone file); dev server with regtest proxies | | `android/` | Android wrapper: a Trusted Web Activity build and a bundled F-Droid flavor | ## Security notes - Self-custody software handling real keys: a hot wallet whose keys live in the browser. Review the code before trusting it with funds; for large amounts use the offline-signing flow or don't keep them in a hot wallet. - **Ark trust model**: the Ark server co-signs off-chain spends and decides off-chain spent/unspent state, but it can never spend your coins, and the pre-signed exit path returns them on-chain without its cooperation. Ark coins expire if never renewed; the wallet renews them automatically. - **Lightning via the Ark server**: payments are HTLC-locked, so the server can fail a payment but not take the funds. - **NWC background answering** means `nwcpush` learns your wallet-service pubkeys and when requests arrive (not their contents). - Your data source sees your addresses and IP; point it at your own node for privacy. Synced state is end-to-end encrypted; relays see only ciphertext and timing. ## License MIT, see [LICENSE](LICENSE).