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 with
Lightning built in, a [email protected] 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, a direct APK at
v3.coinos.io/coinos.apk, and an F-Droid build (io.coinos.fdroid, in review) - Offline:
standalone.htmlis 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.ioby 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
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)
[email protected]: a name claimed on first run that works as a Lightning address (LNURL-pay) and as a BIP-353 DNS payment address (₿[email protected], 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
nwcpushwakes 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://andnostrconnect://, 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.ioby 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/<npub>, /feed/t/<tag>, 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):
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.
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
bun run build # → dist/
dist/index.htmlis a small shell; the app loads asapp-<hash>.jsplus lazily loadedchunk-<hash>.jsfeatures.sw.js,manifest.webmanifestand icons make it an installable PWA that works offline.dist/standalone.htmlinlines 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
- sessionStorage keeps the wallet open across a refresh (cleared on lock or tab close).
- localStorage caches scanned state, feeds and profiles per wallet and network, so a reload paints instantly.
- IndexedDB holds Ark coins, the feed media index and White Noise MLS state.
- 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
nwcpushlearns 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.
