aboutsummaryrefslogtreecommitdiffstats
path: root/build.js
Commit message (Collapse)AuthorAgeFilesLines
* Theme colour at boot: the script that picks light or dark ran before <meta ↵Adam Soltys6 days1-2/+2
| | | | | | | name="theme-color"> existed, so the bar colour stayed #15171a in light mode (a dark status bar over a light page, and the Android bars of the installed web app). It now runs after the PWA head, and light pages get #eef0f3 Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01MgAFLnkF7KrdhuyCLMNgtL
* Build switches answer only to their COINOS_* names: the HAL_* fallback is ↵Adam Soltys6 days1-3/+5
| | | | | | gone from build.js and the README (the cs deploy hook now passes COINOS_TARGET; the F-Droid 1.9 recipe keeps HAL_NO_SW because that tag's own build.js is what builds it, and the recipe says so for the next tag) Co-Authored-By: Claude Fable 5.1 <[email protected]>
* PWA maskable icon is the inner mark on white, like the native app's adaptive ↵Adam Soltys6 days1-1/+1
| | | | | | icon: it was a copy of the full logo, so Chrome's home-screen Coinos showed an extra outer ring Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Setup wizard: a cover behind the avatar — twelve nature covers ↵Adam Soltys7 days1-1/+2
| | | | | | (self-hosted, Unsplash) cycled with arrows or a swipe, or upload your own; untouched, each pubkey gets its own default, published as the kind-0 banner beside the punk Co-Authored-By: Claude Opus 5.5 <[email protected]>
* README rewritten for today's coinos v3; build switches are COINOS_* (HAL_* ↵Adam Soltys9 days1-8/+12
| | | | | | | still accepted); silent-payment leftovers removed Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01YRC6aKhwAud231ot9FPmhi
* Shared note links open on the post, with names and facesAdam Soltys13 days1-1/+5
| | | | | | | | | | | | The server renders /nevent1 and /note1 pages with the thread inlined (post, root, replies, kind 0s) and Open Graph tags; the client seeds its thread cache from it and opens the thread on the first paint. Without it, a pending frame holds the screen instead of the front door. Profile lookups ask our relays and the profile indexes at once, streamed, instead of one after the other behind five-second waits. A thread opened straight from a link has no Back button. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* The standalone wallet opened from a disk shows its avatars, font and languages.Adam Soltys13 days1-3/+9
| | | | | | | | | | With no files beside it, relative punks/, locales/, nip46.js and jsqr.js looked on the disk. From file:// they now come from v3.coinos.io (images and scripts need no CORS; /locales/ now sends Access-Control-Allow-Origin on the server), and standalone.html carries Public Sans inline. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01VP5Ljqk7EYcm5QQAbzDwog
* Serve a shared snapshot for the public feedAdam Soltys14 days1-0/+3
| | | | Build the default Popular feed once on the server and let browsers verify and paint its signed events before slow media finishes. Keep custom relays, pagination, filtering, and relay fallback on their existing paths.\n\nAdd stale-while-refresh persistence, service-worker bypass, development integration, and unit/browser coverage.
* Videos are shrunk on the phone before upload; nostr.build is the first media ↵Adam Soltys2026-09-301-2/+11
| | | | | | | | | | | | | | | | | | | | | | host. No public Blossom host transcodes (nostr.build's and primal's BUD-05 /media return the same bitrate), so a 15-20 Mbps phone clip stalled on playback. src/video-compress.js re-encodes on the device with WebCodecs via Mediabunny (a lazily loaded chunk, left out of the SW precache and the standalone file): H.264, short edge 720, ~2.5 Mbps, moov first, audio passed through, rotation baked in. Anything short of a clear win uploads the original. A toast counts the shrinking down. Media hosts: blossom.nostr.build first, nostr.download second (it was unreachable for hours); a HEAD probe skips a host that doesn't answer within 5 s instead of waiting out the whole upload timeout. video-compress-test: 1080p 20 Mbps 5 s (landscape and rotated portrait) → 1.6 MB H.264 720p 2.49 Mbps with audio, moov first, in 2-3 s. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Du1qT1wbkYwYK6zkGH79Rp
* The Android app's system bars follow the system theme, and the page's theme ↵Adam Soltys2026-09-271-1/+1
| | | | | | | | | | | | | | colour follows the app's. One dark colour for both bars gave white navigation icons over a light page under gesture navigation. The wrapper (1.9, versionCode 10) now uses the page background in light mode and the dark theme colour in dark mode, for the status bar and the navigation bar alike. The page updates its theme-color meta when its own theme is applied, so a PWA's status bar and the wrapper's follow the same switch. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01VhUKyXKtsJ67QAFfb7QR9D
* Public Sans, the coinos.io face, across the app.Adam Soltys2026-09-271-2/+2
| | | | | | | | | One variable-weight woff2 each for upright and italic (41 KB + 45 KB, OFL), shipped with the build and precached by the service worker; the system stack paints first and stays when the file never arrives. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01VhUKyXKtsJ67QAFfb7QR9D
* Paint cached thread counts and header hat on refreshAdam Soltys2026-09-241-1/+3
|
* Build: the point of sale is a real deferred feature — build.js generates ↵Adam Soltys2026-09-211-2/+2
| | | | | | the registry, not features/index.js. Co-Authored-By: Claude Fable 5.1 <[email protected]>
* Android: a reproducible F-Droid flavor with the web app bundled inside.fdroid-v1.6Adam Soltys2026-09-171-1/+6
| | | | | | | | | | | | | | | | | | | | The Android builds so far were shells around the live site: a verified APK proved nothing about the code that ran. The new `fdroid` flavor (io.coinos.fdroid) shares the WebView shell and carries the built web app in its assets. The page still lives at the v3.coinos.io origin — same storage, links and gift URLs — but every file under it is answered from the APK (Assets.java); only the site's live API paths reach the network, and the bundled page registers no service worker (HAL_NO_SW=1). Two clean unsigned builds give the same APK. COINOS_UNSIGNED=1 builds without the keystore; Bun is pinned via packageManager; the Gradle wrapper properties pin 8.12. fdroid/io.coinos.fdroid.yml is the fdroiddata recipe (checksum-pinned Bun in sudo, prebuild web build, scandelete, our signing key allowed for the Binaries match) and docs/REPRODUCIBLE.md the how-to. Verified on a phone: the bundled build cold-starts in airplane mode. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01JuwQqNWtDJcxPWtEm9134z
* A reply or a mention on nostr can buzz your phone.Adam Soltys2026-09-141-2/+6
| | | | | | | | | | | | | The push notifier now watches kind 1 for the user's pubkey beside DMs and zap receipts, and pushes who, what, and an excerpt. The worker shows "New reply to your post" or "You were mentioned" with the text, and a tap lands in the Notifications list — as ?open=notifs when a window has to be opened, or as a worker message when one already is. It has its own switch, "Replies and mentions on nostr", in Settings → Notifications, on by default like payments and DMs. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01QYJfZ4A1TDCEY4Foq3CApR
* A reply can carry a picture, and the WebView build has a name you can say.Adam Soltys2026-09-121-3/+3
| | | | | | | | | | | | The reply box gets the same paperclip the post composer has: one uploader now, handing the URL to whichever draft asked for it, and the reply's own imeta tag describing what it carries. And the UnifiedPush build is graphene.apk — what people will actually call it, and short enough to read down a phone line. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_019mrt228Upn9nNCwGixzSLx
* A second Android build that can wake the wallet, because it holds it.Adam Soltys2026-09-121-2/+7
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | The TWA can't. Chrome renders the site, so the wallet's keys live in Chrome's storage for its own origin, and nothing outside Chrome can reach them — a UnifiedPush message could raise a notification and no more. On a phone with Google Play Services none of this matters; without it, background answering was simply unavailable. So: a second flavour, io.coinos.webview, "Coinos (UnifiedPush)". The wallet runs in a WebView this app owns, which means a push can start it off-screen and let it answer for itself. WakeService loads it with ?wake=<payload> into the same storage the visible app uses, the page subscribes and answers whatever the payload was about, then calls CoinosHost.done() so the service stops — capped at 25 seconds from the page and 30 from the service, because nothing here is allowed to sit in the background costing battery. Its foreground notification says what it's doing rather than apologising for existing. The WebView carries what the wallet actually needs: DOM storage and IndexedDB, the camera for the QR scanner (granted only once Android has granted it to us), a file chooser for attaching pictures, and links to anywhere but coinos.io handed to a real browser. The trade is named in the code and worth naming here: a separate app is a separate wallet. Its storage is its own, so signing in there is signing in again. That's why this is a second build rather than a replacement — the TWA is untouched, still the better browser for anyone whose phone has push. The shared push receiver now asks the system for whatever front door this build has, and asks a per-flavour Wake class whether the wallet can be woken at all — no flavour checks in the middle of it. Both APKs build and ship: coinos.apk as before, coinos-unifiedpush.apk beside it. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_019mrt228Upn9nNCwGixzSLx
* Ship the punks at the size we actually draw them.Adam Soltys2026-09-101-2/+6
| | | | | | | | | | | | | | | | | | | | The art is 240x240 (20KB each, 1.5MB for the set) because that's what a punk published as someone's nostr picture should be — but the circles the app draws are 18 to 64px, so a new wallet with nothing cached was fetching eight times the pixels it could show, one request per face. punks-sm carries a 128px copy of each: 4KB instead of 20KB, indistinguishable at 64px. Small circles and the wizard's picker tiles load those; only the 64px profile avatar and the value the wizard publishes stay full-size. The bigger win is that a punk picture is our own art wherever it turns up. Everyone who keeps the default publishes v3.coinos.io/punks/N.webp as their nostr picture, so those faces now paint straight off disk at the size being drawn — no request, no thumbnail to make, and no broken circle when the legacy coinos.io copies 502 (which they do for a third of the set). Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_019mrt228Upn9nNCwGixzSLx
* Scrub silent payments from everything user-facing — the receive feature is ↵Adam Soltys2026-09-051-1/+1
| | | | | | | gone (no sp in the feature registry) but its mentions lingered: the zapstore description now says plain "full BIP84 bitcoin wallet" (fresh kind 32267 republished, verified clean on the relay), the README loses the sp feature section and its mentions in the E2E-suite and dev-proxy lists, the dead receiveSpTab i18n key is removed, and two stale comments (app.js seams, build.js HAL_FEATURES list) stop naming it. Deliberately KEPT: silentPaymentNote — the send review still labels an output paying someone else's sp1 address, which remains supported Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01MGzU12QrAA4G6BYGzGVdXz
* Android wrapper app (android/): a minimal TWA around v3.coinos.io whose ↵Adam Soltys2026-09-041-0/+9
| | | | | | | whole reason to exist is intent registration — lightning:/bitcoin:/nostr:/lnurl:/lnurlw:/lnurlp: VIEW intents and the same schemes via NFC NDEF dispatch (bolt cards, address stickers; NFC declared required=false), plus verified https links to the site. LauncherActivity forwards a scheme URI into the web app as ?u=<encoded>; the web app reads it BEFORE features construct — a nostr npub/nprofile rewrites itself onto the existing /npub profile deep link, payment URIs stash and dispatch through handleScanned (the QR path) once a wallet is open, and the param is stripped from the URL either way. assetlinks.json (static/.well-known/, copied by build.js) vouches for the signing cert so the TWA runs URL-bar-less; the built APK is served at /coinos.apk for sideloading. Signing keystore lives in android/ GITIGNORED (keystore.properties beside it) — the assetlinks fingerprint pins it, so losing it means re-shipping assetlinks too. Verified: headless ?u= routing (lightning → Send with invoice filled + URL cleaned; nostr → profile page), APK installed on a real phone via adb, schemes registered (chooser appears where other wallets also claim lightning:) Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01MGzU12QrAA4G6BYGzGVdXz
* Nostr signature verification moves off the main thread: the pool's ↵Adam Soltys2026-09-031-1/+18
| | | | | | | synchronous verifyEvent (a schnorr check per event, ~560ms on a relay backlog — the profiled carousel-stutter cause) is switched off, and every event is now gated through a Web Worker before any caller sees it. Nothing unverified is ever trusted — the worker runs the same nostr-tools verifyEvent, just on another thread, with a synchronous fallback if the worker is unavailable (standalone file://) or stalls. Bundled to dist/verify-worker.js, precached in the SW Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01MrkLsNScXe9b6BviUSdmMr
* The single-file wallet returns as a first-class artifact: every build now ↵Adam Soltys2026-09-021-0/+8
| | | | | | | emits standalone.html (~950KB, everything inlined, boots from a bare file:// with no server), How it works grows a 'Keep a copy' card linking the download, and the tab strip's phantom double-tap dies — the pointerdown's paired click was arriving after the render had already swapped in fresh handlers, and with toggling tabs that ghost click un-did the tap it belonged to. The timestamp guard now lives on the DOM node, which survives the morph Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01MrkLsNScXe9b6BviUSdmMr
* The sticker gets specific: v3 betaAdam Soltys2026-08-281-1/+1
| | | | | Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_019eA8JRq9Wr7z2fRQk99HLi
* The logo wears its beta sticker — purple, tilted like staging's orange ↵Adam Soltys2026-08-281-3/+6
| | | | | | | one, present from the static boot shell's very first paint so it never pops in Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_019eA8JRq9Wr7z2fRQk99HLi
* The bundle knows its own address: boot-critical references (entry module, ↵Adam Soltys2026-08-281-4/+8
| | | | | | | manifest, service worker, touch icon) are root-absolute — a page served from /ag/… was resolving them into the subpath, getting index.html back, and dying silently on module MIME checking before a single render Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_019eA8JRq9Wr7z2fRQk99HLi
* The app arrives in movements: real ESM chunks split the bundle (entry 333KB, ↵Adam Soltys2026-08-261-19/+63
| | | | | | | shared chunks alongside, 61KB of gifts/NWC/hats deferred past first paint into slots that keep their hook seniority), the lazy scripts wear IIFE coats so their names can't collide with the page's — which is what silently broke the remote-signer button — nip98 moves out of the login module so minimal builds stop hauling backup relays, and the smoke test learns the front door's new steps while the regtest stack rises from its IBD nap Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_019eA8JRq9Wr7z2fRQk99HLi
* The page arrives light and paints at once: index.html shrinks to 46KB with a ↵Adam Soltys2026-08-261-18/+84
| | | | | | | static boot shell baked in — brand row, and the profile frame when the path names one — while the 789KB bundle moves to a content-addressed app-<hash>.js the browser keeps (a repeat visit re-downloads kilobytes, not the app); the nostr-tools barrel import goes subpath, and the NIP-46 signer client ships as its own lazy file fetched on first bunker login Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_019eA8JRq9Wr7z2fRQk99HLi
* The footer says what it stamps: the word commit, the short hash git actually ↵Adam Soltys2026-08-181-1/+13
| | | | | | | knows, and a link to it — the build token stays where it belongs, inside the service worker Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01JtaatFfAbx91Nh3xqKVqKz
* The service worker learns what must stay live: same-origin API paths — ↵Adam Soltys2026-08-181-1/+7
| | | | | | | esplora, sp, lnurl, well-known — go straight to network, never the cache that froze a refresh on a stale unconfirmed Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01JtaatFfAbx91Nh3xqKVqKz
* Staging wears its name on the logo and deals in play money: ↵Adam Soltys2026-08-161-5/+23
| | | | | | | HAL_TARGET=staging stamps the brand, renames the PWA, and starts fresh wallets on Mutinynet — one master, no diverging branch Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01JtaatFfAbx91Nh3xqKVqKz
* Hats: put your money where your head is — supporter hats perch above the ↵Adam Soltys2026-08-161-1/+1
| | | | | | | avatar everywhere it appears, from 21-sat beanie to 2.1M-sat top hat; the crown stays on the founder Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01JtaatFfAbx91Nh3xqKVqKz
* Settings grows a Notifications page — per-category switches the notifier ↵Adam Soltys2026-08-141-2/+2
| | | | | | | honours server-side — and the status bar gets a real coinos badge instead of a black blob Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_014tE4U5nEjGTQy8PwYqamfv
* Park the send field above the phone keyboard; wizard whenever walletlessAdam Soltys2026-08-041-1/+1
| | | | | | | | | | | | The recipient candidate list was hiding under the on-screen keyboard: scroll the field to the top on focus and when fresh candidates land, give the list scroll room to get there, and let the keyboard resize the viewport (interactive-widget) so the scroll has somewhere to go. The wizard now shows whenever the device has no wallet — the one-time onboarded flag is gone; deleting every wallet brings the wizard back. Co-Authored-By: Claude Fable 5 <[email protected]>
* A deploy shouldn't refresh the page out from under youAdam Soltys2026-08-031-8/+34
| | | | | | | | | | | | | | | | | | | | | | The app is inlined in the page, so when a new service worker took control the page reloaded — otherwise it would keep running the old build. But navigations are network-first: the refresh that triggered the update had already fetched the new page, so the reload landed a second or two later as an unasked-for second refresh. The page and the worker now carry the same build token and the page asks which build took over, reloading only on a real mismatch. The token is hashed before substitution, since a page cannot contain a hash of itself. tools/sw-reload-test.js pins all three cases against a swappable server: a first visit doesn't reload itself, a refresh after a deploy is the only refresh, and a page left sitting open across a deploy is still reloaded — exactly once. The previous build takes two loads for one manual refresh; this one takes one. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Fix the service-worker ReferenceError; stop dialing dead relaysAdam Soltys2026-08-031-3/+3
| | | | | | | | | | | | | | | | | | | | | | | sw.js threw "NWC_WAKE_FALLBACK is not defined" on every load: the template substitution used String.replace with a string pattern, which replaces only the FIRST match — and that was the mention inside the comment above, leaving the real placeholder literal in the output (parsed as a block plus a bare identifier). replaceAll fixes it, the comment no longer spells the token, and {{VERSION}} got the same treatment. Both build profiles now emit a service worker that parses and activates cleanly. The relay noise: a browser logs failed WebSocket connections itself and JS cannot suppress that line, so the fix is to stop making the doomed connection. nostr.js now probes each new relay once, remembers unreachable ones for ten minutes, and filters them out of every subscribe/query/publish (never down to an empty set). Publish rejections that look like connection failures mark the relay sick rather than logging a scary "refused" line. relay.damus.io — down all week — also leaves the profile relay list. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Self-host the punks; skipping avatar still publishes your punkAdam Soltys2026-08-031-0/+3
| | | | | | | | | | | | | coinos.io serves 502 for a third of the punk set (both the numbered and hash endpoints), which is why a default avatar could come up blank. The 64 webps now ship with the app (53 salvaged, 11 gaps filled with donors) and every reference is same-origin. Skipping the avatar step publishes the deterministic punk as the kind-0 picture — your face is the same on every nostr client. Success screen: checkmark now sits below the title, bigger. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Rebrand: hal → coinos v3Adam Soltys2026-08-021-1/+1
| | | | | | | | | | | | The app already presents as Coinos; this finishes the job in the repo: package name, README, GitHub link, code comments, and the stale locale lines that still explained the old "Hal Wallet" name (they fall back to the English tribute line). Kept: the hal-nwc-pouch IndexedDB name (an upgrade-compat identifier), the halwallet.app domain in the BIP-353 names feature (live infrastructure), and the Hal Finney tribute itself. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Chat: Concord encrypted community messaging, with a default coinos roomAdam Soltys2026-08-021-1/+1
| | | | | | | | | | | | Implements the Concord protocol (CORD-01..04) — private streams, the control-plane fold, guestbook membership, owner-rooted authority — and ships a built-in "coinos" community founded by Adam's npub (owner/admin). Messages sign with the nostr login identity when a signer is live, else the wallet's NIP-06 key; all encryption rides derived stream keys so remote signers only ever sign seals. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* One wallet per Nostr identity, NIP-98 auth, npub payment addressesAdam Soltys2026-08-011-1/+1
| | | | | | | | | | | | | | | | | | | | | | | | | | Consistency fix with teeth: logging in with a pasted key derived one wallet while an extension login for the SAME identity found no backup and generated a different one. Now every signer kind runs one flow — look for the published association first (that's where the money is), then derive (key in hand) or generate (signer only) — and a derived seed is published too. That costs nothing, since the derivation is public and anyone holding the key could compute it anyway, and it makes the identity open the same wallet however you log in. Registrar auth is now NIP-98 (HTTP Auth, kind 27235) instead of a homemade envelope: the signature binds the URL, method and body hash, so a captured header can't be replayed elsewhere. NIP-78 already covered the encrypted association; NIP-26 delegation is deprecated, so owner→manager nomination lives in the registrar's own model: an npub-shaped name may only be claimed by that identity, which nominates the wallet key to keep the record updated afterwards (the wallet key is the only one always available — offline, in the worker). Payment addresses therefore default to the user's REAL npub in all three login methods, falling back to the wallet's own identity when no Nostr account is linked. Co-Authored-By: Claude Fable 5 <[email protected]>
* Log in with Nostr, and link a wallet to a Nostr accountAdam Soltys2026-08-011-1/+1
| | | | | | | | | | | | | | | | | | | | | | | | | Three ways in: a NIP-07 browser extension, a NIP-46 remote signer (Amber, Amethyst — paste its bunker:// address), or a pasted nsec/hex key that lives in memory for the session and is never stored. How an account gets a wallet depends on what we can see, and the two cases are genuinely different: * We hold the key (pasted nsec): the seed is DERIVED from it — sha256("coinos:wallet-seed:v1" | privkey) as BIP-39 entropy. The same Nostr key always opens the same wallet, on any device, with nothing stored anywhere. * We only have a signer: deriving is impossible (we never see the key, and neither signatures nor NIP-44 ciphertexts reproduce), so the seed is ASSOCIATED instead — generated once and published as a replaceable event encrypted to the user's own key, which only their signer can decrypt. Later logins find and decrypt it. The second path means the Nostr key can recover the money; the UI says so before publishing anything. Linking an already-open wallet uses the same publish path and refuses to overwrite an account's existing wallet. Co-Authored-By: Claude Fable 5 <[email protected]>
* Coinos branding: logo and black & white themeAdam Soltys2026-08-011-7/+3
| | | | | | | | | Icons, favicon, and PWA assets come from the coinos brand set; the accent goes monochrome (black in light mode, white in dark, with a proper on-accent text variable so buttons stay legible both ways) and the inline bitcoin mark inherits the text color. Co-Authored-By: Claude Fable 5 <[email protected]>
* Coinos v3: rebrand from Hal, payment addresses move to @coinos.ioAdam Soltys2026-08-011-5/+5
| | | | | | | | | | | | | | | The wallet is the next version of coinos, deployed at v3.coinos.io (same build as halwallet.app) ahead of an eventual migration of coinos.io itself. App name, manifest, notifications, and the NWC alias all say Coinos; the Hal Finney tribute stays. The registrar is multi-domain (per-domain Cloudflare zones; existing halwallet.app names keep working) and new claims default to @coinos.io. Names belonging to existing coinos users are reserved for them — the registrar checks the live coinos API before granting a claim — until the migration gives those users a way to claim their own. Co-Authored-By: Claude Fable 5 <[email protected]>
* Payment names: ₿[email protected] via BIP-353 DNS payment instructionsAdam Soltys2026-08-011-1/+1
| | | | | | | | | | | | | | | | | | | | Receiving: a Settings card claims a username, signed with the wallet's nostr key; the new names registrar (names/server.js, deployed at names.halwallet.app) enforces first-come ownership by that key and mirrors the claim into a DNSSEC-signed TXT record at <name>.user._bitcoin-payment.halwallet.app whose BIP-21 URI carries the wallet's reusable ark address. The record re-registers itself if the ark address ever changes, and a collapsible hint shows the single CNAME a user adds to serve the same name from their own domain (BIP-353 supports delegation when both zones are DNSSEC-signed). Sending: a pasted name@domain resolves over DNS first — two independent DoH resolvers must agree and both report the DNSSEC AD flag (full client-side chain validation is the stated follow-up) — an ark= instruction pays natively, an on-chain path fills the send form, and a name with no DNS record falls back to the Lightning-address zap flow. Co-Authored-By: Claude Fable 5 <[email protected]>
* Simplify: one Lightning path, one balance, fewer featuresAdam Soltys2026-08-011-21/+1
| | | | | | | | | | | | | | | | | | | | | | | | | Dropped: silent payments (feature, wallet chains, scan worker, indexer config), Boltz submarine/reverse swaps (feature + swap.js + tools), the third-party ark swap bridge (config, client flows, settings card — the ASP's own Lightning send is the one path now), and the Consolidate coins settings action. The NWC pouch is gone with them: background answering now spends the WHOLE ark balance. The IndexedDB record mirrors the real manager state plus the chain-3 keys for its live coins and a change-index window; the worker pays through the ASP exactly like the open app, and the app folds the worker's spends, change, and key allocations back in on connect (additive merge + reconcile against the server). A leftover chain-6 pouch from the short-lived pouch era is swept home automatically using the keys stored in its own record. The stated tradeoff: same-origin script access now reaches the ark balance, not a carve-out — the ark balance is walking-around money, the on-chain wallet's keys are never mirrored, and per-connection NWC budgets remain the enforced ceiling. Bundle: 726KB -> 602KB. Co-Authored-By: Claude Fable 5 <[email protected]>
* Background NWC: the worker now answers — and pays — from the pouchAdam Soltys2026-08-011-35/+57
| | | | | | | | | | | | | | | | | | | | | | | The no-tap half of background wallet-connect. The push payload carries the (encrypted, client-signed) request event; a bundled service-worker responder (src/sw-nwc.js -> nwc-respond.js) finds the matching pouch in IndexedDB, decrypts with the connection's service key, and answers get_info/get_balance/list_transactions from pouch state. pay_invoice pays through the swap bridge with an ArkManager running over the pouch's chain-6 key window (one HTLC lock, one HTTP swap), enforcing per-payment and daily budgets from the pouch's own spend log, checking the notifier's /answered endpoint so it never double-pays or shouts over an open device, and persisting the swap before handing it off so the app can refund a crashed flight. Anything it can't handle falls back to the wake-the-user notification. The notifier grows /publish (verified 23195s only, rate-limited) as the worker's reply pipe and /answered for race avoidance. The app gains pouch funding (mailbox-less arkoor self-send whose recipient bytes we keep), emptying, spend/budget reconciliation on open, and refund recovery with the pouch's own keys. Co-Authored-By: Claude Fable 5 <[email protected]>
* Wake hal for wallet-connect requests while it is closedAdam Soltys2026-08-011-0/+35
| | | | | | | | | | | | | | | | | | | Step 3, first half. Android Chrome freezes backgrounded PWAs and closes their relay sockets, so a phone with hal "open" answers nothing — which is what made Amethyst spin. Push is the only way to reach a frozen app. - service worker gains a `push` handler. It deliberately does NOT spend: if a window is open it nudges it to re-listen, otherwise it raises a notification that opens hal on tap. Putting a spend path in the worker is the remaining half and wants its own testing. - hal-side: request permission, subscribe with the notifier's VAPID key, and register its service pubkeys with nwcpush.coinos.io. Turning it off deregisters, unsubscribes, and clears the pouch — no reason to leave spendable keys where a worker can read them if nothing will. - a Settings toggle, and the ark feature exposes the ASP context the pouch record needs. Co-Authored-By: Claude Fable 5 <[email protected]>
* Actually ship the NWC feature, and guard against the drift that hid itAdam Soltys2026-08-011-2/+4
| | | | | | | | | | | | | | build.js swaps src/features/index.js for a generated module listing only the features in its own ALL_FEATURES registry, so registering nwc in index.js alone was not enough: its i18n strings shipped (they live elsewhere) but its code never entered the bundle, and the settings card silently never rendered. - add nwc to build.js ALL_FEATURES, after ark since it drives ark's headless pay/balance seam - tools/feature-registry-check.js asserts the two lists agree on membership AND on construction order, since hook precedence depends on the order Co-Authored-By: Claude Fable 5 <[email protected]>
* Add NIP-57 Lightning zaps (npub / lightning address / lnurl)Adam Soltys2026-07-301-2/+2
| | | | | | | | | | | | | | | | | | | | | | | | | | Send a Lightning zap to a nostr npub, a lightning address (name@domain), or an lnurl. New src/lnurl.js does the resolution half — target → LNURL-pay params → signed kind-9734 zap request → callback → bolt11 — and the new zaps feature (src/features/zaps.js) drives the amount/comment UI, then hands the invoice to the existing Boltz submarine-swap flow via a new swaps.startLnPay hook, so there's one tested pay/review/confirm/success path. Routing: a bolt11 is still caught by swaps; an npub still prefers an instant free Ark zap when Ark can serve it, and only falls through to a Lightning zap otherwise — ark's "no Ark address" screen also offers an explicit "Zap over Lightning" fallback (gated on the zaps feature's canLnZap). A real zap receipt is attributed only when the server advertises allowsNostr and we know the recipient's pubkey; otherwise it degrades to a plain LNURL-pay. Nostr seam gains nostrSign (finalize without publishing), nostrProfile (kind-0 with lud16/lud06), and nostrRelays. Lightning addresses are debounced in matchSendText since their regex matches mid-typing. Verified: unit tests for parse/encode/zap-request; a live resolve→signed 9734→payable mainnet bolt11 against coinos; and an in-browser DOM smoke (zap form + LNURL params over CORS + allowsNostr hint + handoff). Co-Authored-By: Claude Opus 4.8 <[email protected]>
* Gift cross-device sync, snapshot size cap, reliable SW updates, gift-detail ↵Adam Soltys2026-07-061-1/+8
| | | | | | | | | | | | | | | | | | | | | layout - Gifts now sync across devices: gift records + reserved/reclaimed coin sets ride the encrypted snapshot as a merge-safe extension (like ark), and every gift state change (create/reclaim/revoke) triggers a saveCache so it actually publishes. A gift made on one device now shows on the others. - Cap the published snapshot at ~40KB JSON (relay rejects >64KB events, nip44 expands ~1.5x): trim oldest on-chain txs when oversized; another device backfills via rescan. Typical wallets untouched. - Service worker: register with updateViaCache:'none' so a new build is detected immediately (not held by the 4h HTTP cache), and reload once on controllerchange so the fresh app runs without a manual second refresh. - Gift detail: View/Reclaim share a row, Revoke on its own full-width line so 'Revoke now' stops wrapping. Co-Authored-By: Claude Fable 5 <[email protected]>
* Features phase 2b: silent payments + nostr sync leave the core walletAdam Soltys2026-07-041-1/+1
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | wallet.js drops from 2,374 to ~1,970 lines, holding only BIP84 on-chain logic plus generic feature seams. New seams added this pass: registerBalanceExtra extra confirmed/pending buckets registerInputProvider extra spendable inputs for buildTx registerSendPreparer destinations needing post-selection derivation registerInputSigner non-BIP84 input signing registerHistoryProvider / registerScanHook / registerRealtimeHook / registerLoadHook / registerCacheExtension / registerCacheSavedHook src/features/sp-wallet.js: the whole BIP-352 engine — key derivation, worker-accelerated indexer scanning, the live indexer WebSocket, SP outputs as spendable inputs, sp1 send preparation, and per-output-key signing — installs via those seams (installSpWallet). src/features/sync.js: nostr identity + encrypted cross-device state sync plus its Settings card, installed via load/cache-saved/realtime hooks. profileChip/nostrProfile move here-adjacent (into gifts, the only other nostr consumer), so core app.js no longer imports nostr at all. buildTx/sign are now feature-agnostic (input providers + preparers + signers); _recomputeBalance* use the balance-extra registry, so a core build with no SP no longer calls a missing spBalance() (that regression dropped the minimal build into offline mode — caught by the smoke test). New coverage: tools/sp-test.js drives two browser "devices" through an SP send, indexer discovery, and an SP-input spend on the ark regtest chain (needs tools/sp-indexer.js pointed at bitcoind:18543). Minimal smoke now also asserts no SP/nostr code ships. Minimal profile: 314KB, down from 412KB (full: 580KB). All suites green: minimal smoke, sp-test, ark UI, gift lifecycle. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01QFAcD4V284DvYZZHP7Rxxy