1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
|
# 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 `[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](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`)
- **`[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 `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/<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):
```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-<hash>.js` plus
lazily loaded `chunk-<hash>.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).
|