aboutsummaryrefslogtreecommitdiffstats
path: root/README.md
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-07-05 23:27:12 -0700
committerAdam Soltys <[email protected]>2026-07-05 23:27:12 -0700
commita3783d7e7f53709a8f8352494b9e89583e46f85b (patch)
treedc8ad0c8aef4f447537110b44a2274764acdabdf /README.md
parent81a55be4ea07d4dee4a83a3728c8ba0914744cd5 (diff)
downloadcoinosv3-a3783d7e7f53709a8f8352494b9e89583e46f85b.tar.gz
coinosv3-a3783d7e7f53709a8f8352494b9e89583e46f85b.zip
Docs: How-it-works covers Lightning/Ark/SP/gifts; README rewrite
The in-app page grows plain-language cards for Lightning swaps, Ark (instant off-chain, the unilateral escape hatch, expiry housekeeping, fees), silent payments, and gift links. The README catches up with reality: the native-JS Ark client headlines the feature list, and the modular HAL_FEATURES build system gets its own section — every feature is a plugin behind a build seam, and excluded code never ships. Co-Authored-By: Claude Fable 5 <[email protected]>
Diffstat (limited to 'README.md')
-rw-r--r--README.md193
1 files changed, 125 insertions, 68 deletions
diff --git a/README.md b/README.md
index 12ba691..c15bf76 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,8 @@
A self-contained, **single-file** Bitcoin wallet that runs entirely in the
browser — think bitaddress.org, but a modern BIP84 HD wallet from a seed phrase
-that scans history, watches for payments, and spends. The whole wallet is one
+that scans history, watches for payments, spends on-chain, pays Lightning
+invoices, and holds an off-chain balance on **Ark**. The whole wallet is one
static `index.html` you can save and run offline, forever.
Live at **https://halwallet.app** (the built `index.html` also runs straight
@@ -12,42 +13,98 @@ The name is a nod to [Hal Finney](https://en.wikipedia.org/wiki/Hal_Finney_(comp
## Features
+### Core (always included)
+
- **BIP84 / native SegWit (p2wpkh)** HD wallet from a 12-word BIP39 seed
- (imports any valid BIP39 phrase; optional passphrase). Mainnet.
+ (imports any valid BIP39 phrase; optional passphrase).
+- **Per-wallet networks** — each wallet is stamped with its network (mainnet,
+ testnet, signet, mutinynet, regtest); switching wallets switches networks,
+ and wallets on different networks coexist in one session.
- **One fresh address at a time** — a new receive address is only handed out
after the current one is paid, so used addresses stay contiguous and scans
stay tiny (no 20-address gap probing).
-- **Choose your own block explorer** — mempool.space by default (with
- blockstream.info as a silent failover), blockstream.info only, or a **custom
- Esplora / electrs REST URL** (e.g. your own node) in Settings.
-- **Real-time** via the mempool.space WebSocket (when that explorer is
- selected): an incoming payment shows a "Payment received!" screen the moment
- it hits the mempool. Other explorers fall back to polling plus a periodic full
- reconcile. A ping/pong heartbeat keeps the socket alive (auto-reconnect on
- drop, sleep, or network change).
-- **Spending** with proper coin selection + fee estimation (`@scure/btc-signer`
- `selectUTXO`), multiple recipients, a fee-rate picker, send-max, and manual
- coin control.
-- **QR scanner** on the send page — scan a Bitcoin address, a BIP21 URI (fills
- the amount too), or a signed transaction to broadcast it. Uses the native
- `BarcodeDetector` where available and lazy-loads jsQR otherwise.
-- **Optional encrypted cross-device sync over Nostr** — wallet state is
- NIP-44-encrypted to yourself and stored as a replaceable event (kind 30078) on
- the relay(s) you choose (default relay.coinos.io). The Nostr identity is
- derived from the same seed (NIP-06). On by default, toggled off in Settings.
-- **Offline / air-gapped signing** — export a keyless JSON snapshot of your
- coins on an online device, import it on an offline one, sign locally, and
- export the signed transaction (hex / file / QR) to broadcast elsewhere. You
- can also sign-and-export online (broadcast from another device).
-- **Installable PWA** — installs to your home screen and works offline once
- installed (the app shell, icons, and QR decoder are precached).
-- **Global sats/BTC toggle** (defaults to sats) — every amount label is
- clickable to switch, persisted across sessions.
+- **Choose your data source** — Electrum-over-WebSocket servers or Esplora
+ REST explorers, per network, with silent failover — or point it at your own
+ node.
+- **Real-time** incoming-payment push over WebSocket where the source supports
+ it, with polling + periodic reconcile as the fallback. Replaced (RBF)
+ transactions are detected and pruned from history automatically.
+- **Spending** with proper coin selection + fee estimation
+ (`@scure/btc-signer`), multiple recipients, a fee-rate picker, send-max,
+ manual coin control, and a QR scanner (address / BIP21 / signed tx).
+- **Offline / air-gapped signing** — export a keyless snapshot on an online
+ device, sign on an offline one, broadcast anywhere.
+- **Installable PWA**, global sats/BTC toggle, 19 languages.
+
+### Ark — instant off-chain payments (feature: `ark`)
+
+A **native-JS Ark client** (speaks gRPC-web + MuSig2 directly to
+[bark/captaind](https://github.com/ark-bitcoin/bark) servers — no WASM, no new
+dependencies, ~2k lines):
+
+- **Board** on-chain coins into an Ark server; **send and receive instantly**
+ off-chain with zero mining fees; server fees shown before anything moves.
+- **Refresh / consolidate** vtxos (the round-based maintenance that resets
+ expiry and chain depth).
+- **Collaborative offboard** — move the whole Ark balance back to your own
+ on-chain address in one server-cosigned transaction.
+- **Unilateral exit** — the trustless backstop: publish the vtxo's pre-signed
+ transaction chain on-chain (CPFP-bumped, TRUC-aware, one confirmed hop at a
+ time), wait out the CSV timelock, and claim with your key alone. Works even
+ if the server is gone; live progress (hop counter, blocks-left countdown) in
+ Settings.
+- **Ark gifts** — bearer gift links whose secret *is* the link: claimed
+ instantly and free, even into a brand-new empty wallet with zero on-chain
+ footprint. Revocable until claimed.
+- Crash-safe by construction: every multi-step ceremony checkpoints to storage
+ around each server round-trip, and local state reconciles against the
+ server's authoritative vtxo status before money moves.
+
+### Lightning (feature: `swaps`)
+
+- Pay bolt11 invoices and receive over Lightning via **Boltz-compatible
+ submarine swaps** — non-custodial either way: a provider can fail a swap but
+ never take funds (claims/refunds are enforced on-chain). Pick a provider per
+ network or point at your own.
+
+### Silent payments (feature: `sp`)
+
+- A static, publicly shareable **BIP-352 silent-payment address** (`sp1…`);
+ every payment lands at a fresh unlinkable on-chain address. Scanning uses a
+ configurable (self-hostable) tweak indexer, with the EC math in a Web Worker
+ and a chunked, resumable catch-up scan.
+
+### Gifts (feature: `gifts`)
+
+- **Gift links** — presigned bearer transactions claimable by whoever opens
+ the link, straight into a fresh wallet (with seed backup flow) or an
+ existing one. Reclaim or revoke unclaimed gifts. Optionally **lock a gift to
+ a nostr account** (the claim code is DM'd to them; the link alone is not
+ enough).
-Built with the audited [`@scure`](https://github.com/paulmillr/scure-btc-signer)
-/ [`@noble`](https://github.com/paulmillr/noble-hashes) libraries plus
-[`nostr-tools`](https://github.com/nbd-wtf/nostr-tools). Pure JS, no
-`crypto.subtle`, so it works from `file://` with no server.
+### Cross-device sync (feature: `sync`)
+
+- Optional **encrypted state sync over Nostr** — wallet state NIP-44-encrypted
+ to yourself as a replaceable event, per network, on relays you choose. The
+ nostr identity derives from the same seed (NIP-06).
+
+## Modular builds
+
+Every feature above is a **plugin behind a build seam**. `HAL_FEATURES`
+selects what ships; excluded features never enter the bundle — their code,
+their network endpoints, their crypto:
+
+```bash
+bun run build # everything (~610 KB)
+HAL_FEATURES=ark,gifts bun run build # core + ark + gifts
+bun run build:minimal # core on-chain wallet only (~320 KB)
+```
+
+`tools/minimal-smoke.js` verifies the minimal profile is a fully working
+on-chain wallet with zero feature code in the bundle. Features integrate
+through fixed seams on the core (`registerCoinLock`, `registerInputSigner`,
+cache extensions, UI hooks), so a stripped build isn't a crippled build — it's
+the same core wallet, smaller.
## Develop
@@ -58,6 +115,12 @@ bun install
bun run dev # http://localhost:5173 (rebuilds on each refresh)
```
+The dev server also proxies local regtest backends (electrum, esplora, boltz,
+SP indexer) so the app works unchanged from a phone on the LAN. End-to-end
+suites in `tools/` drive a headless Chrome through the real flows (boarding,
+ark sends, offboard, unilateral exit, gifts, swaps, silent payments) against a
+local regtest stack.
+
## Build
```bash
@@ -76,50 +139,44 @@ Three layers, fastest first:
1. **sessionStorage** — keeps the wallet open across a refresh (cleared on
logout / tab close).
-2. **localStorage** — caches scanned state (addresses, UTXOs, history) keyed by
- a hash of the seed, so a reload shows balances instantly.
-3. **Nostr** — encrypted, replaceable cross-device state on your configured
- relay(s) (when sync is enabled).
-
-On load and on a 2-minute timer the wallet does a full reconcile; between those,
-a light poll re-checks the fresh frontier and the addresses currently holding
-coins. A manual **Settings → Rescan** forces a full re-scan on demand.
-
-## Offline / air-gapped spending
+2. **localStorage** — caches scanned state per wallet *and per network*, so a
+ reload shows balances instantly.
+3. **Nostr** — encrypted, replaceable cross-device state per network (when
+ sync is enabled).
-1. **Online device:** open the wallet, let it scan, then **Settings → Export
- snapshot** (a keyless JSON of your UTXOs + fee rates).
-2. Move the file to an **offline device** running the saved `index.html`.
-3. There, enter your **seed phrase** (it auto-detects no network), then
- **Settings → Import snapshot**.
-4. Build and **sign** a transaction; copy/download the signed hex (or scan the
- QR) and broadcast it from any online device.
+On load and on a timer the wallet reconciles against its data source; a manual
+**Settings → Rescan** forces a full re-scan on demand.
## Layout
| File | Purpose |
| --- | --- |
-| `src/wallet.js` | BIP84 derivation, scanning, coin selection, signing, realtime, cache, sync |
-| `src/api.js` | Esplora wrapper + explorer selection (mempool / blockstream / custom) with throttle/cooldown/timeout |
-| `src/nostr.js` | Optional encrypted cross-device state sync over Nostr (configurable relays) |
-| `src/scan.js` | Camera QR scanner (native BarcodeDetector / lazy jsQR) |
-| `src/app.js` | UI controller (vanilla DOM) |
-| `src/qr.js` | QR → SVG (zero-dep) |
-| `src/format.js` | sat/BTC formatting helpers |
-| `src/i18n.js` | UI strings + translations |
-| `src/style.css` | Hand-rolled styles |
-| `build.js` / `dev.js` | Bun bundler → inlined `index.html` + PWA sidecars, and dev server |
+| `src/wallet.js` | BIP84 core: derivation, scanning, coin selection, signing, realtime, cache — plus the feature seams |
+| `src/api.js` | Data-source selection per network (electrum/esplora presets, throttle/cooldown/failover) |
+| `src/ark/` | Native-JS Ark protocol: gRPC-web codec, arkoor sends, boarding, refresh, offboard, unilateral exit, validation, crash-safe manager |
+| `src/features/` | Feature plugins: `ark`, `swaps`, `gifts`(+`gifts-wallet`), `sp`(+`sp-wallet`), `sync` — and `index.js`, rewritten at build time by `HAL_FEATURES` |
+| `src/swap.js` | Boltz submarine/reverse swap engine |
+| `src/silentpay.js` / `src/sp-worker.js` | BIP-352 math + its Web Worker |
+| `src/nostr.js` | NIP-44 state sync, NIP-17 DMs, profiles |
+| `src/electrum.js` / `src/scan.js` / `src/qr.js` | Electrum transport, camera QR scanner, QR rendering |
+| `src/app.js` | UI controller (vanilla DOM) + feature registry |
+| `build.js` / `dev.js` | Bun bundler → inlined `index.html` + PWA sidecars; dev server with regtest proxies |
## Security notes
-- Self-custody software handling real keys — review the code before trusting it
- with funds, and prefer an offline machine for the seed phrase. It's a hot
- wallet (keys live in the browser); for large amounts use a hardware signer or
- run this air-gapped on a dedicated offline device.
+- Self-custody software handling real keys — review the code before trusting
+ it with funds. It's a hot wallet (keys live in the browser); for large
+ amounts use a hardware signer or run it air-gapped.
- The seed/passphrase live in memory while the page is open and in
sessionStorage for the tab session; locking or closing the tab clears them.
-- The localStorage cache and the Nostr event hold public chain data (addresses,
- UTXOs, amounts); the Nostr copy is encrypted and keyed to your seed-derived
- identity, but a relay can see that an identity is publishing app data.
-- Whichever block explorer you query sees your addresses and IP — point it at
- your own node for full privacy.
+- **Ark trust model**: off-chain sends are co-signed by the Ark server, which
+ is the arbiter of off-chain spent/unspent state — but it can never spend
+ your coins, and the pre-signed exit path means you can always return
+ on-chain without its cooperation. Ark coins expire (weeks) if never
+ refreshed; the wallet surfaces this, but don't park long-term savings in an
+ Ark.
+- Swap providers are non-custodial by protocol: worst case a swap fails and
+ refunds on-chain.
+- Whichever data source you query sees your addresses and IP — point it at
+ your own node for full privacy. The localStorage cache and the Nostr event
+ hold public chain data; the Nostr copy is encrypted end-to-end.