aboutsummaryrefslogtreecommitdiffstats
path: root/src/features
Commit message (Collapse)AuthorAgeFilesLines
* Push notifications for payments and messages; offline Spending noticeAdam Soltys2026-08-021-0/+67
| | | | | | | | | | | | | | | | | | | | | | | The nwcpush notifier grows a second registration kind alongside NWC: a notify watch of what it can see without keys — kind-1059 wraps p-tagged at the user (DMs, direct invites), zap receipts (9735/9737), and wraps authored by the user's community channel keys (chat). It pushes a typed nudge, throttled per device (chat bursts collapse); the service worker shows a generic notification unless a window is visible, and tapping opens the app. The names registrar reports settled lightning-address receives server-to-server (token-gated /notify), so the common payment path notifies even with no nostr event in sight. The app registers silently whenever permission is already granted and keeps the watch current as communities change; a dismissible card on the chat home offers to enable it otherwise. Ark zap receipts (9737) join the relay whitelist so the payment watch can see them. Also: the offline Spending receive pane now says you're offline and offers a one-tap switch to Savings instead of spinning forever. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Avatars: no punk flash while profiles loadAdam Soltys2026-08-021-8/+36
| | | | | | | | | | | The punk is a statement about having no picture, not a loading state — in-flight profiles show a quiet empty circle instead. Known profiles persist (capped at 150, daily background refresh) so your own avatar and regular correspondents paint correctly on the first frame of the next session. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Sync just works: NIP-65 relays, no card; gift-locking leftovers goneAdam Soltys2026-08-021-50/+39
| | | | | | | | | | | | | | | The Device sync card and toggle are gone — sync is always on, riding the user's own NIP-65 (kind 10002) write relays: the login npub's first, else the wallet key's, coinos relay as the fallback and always included. A fresh wallet identity with no NIP-65 publishes one (default relay) so other clients can find its events. The npub-locked-gift remnants go with it: the per-wallet nostr address card that pitched gift-locking, the how-it-works sentence, the dead NostrSync.sendDM/sendNostrDM path, and the orphaned claim-code field. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Settings: drop the nostr address and Spending balance cardsAdam Soltys2026-08-021-50/+0
| | | | | | | | | | | The npub lives on the profile page now, and the ark card's remaining jobs are covered elsewhere — status on the balance card, the unilateral exit behind the move panel's link. Payments keeps just the payment address; the per-wallet screen (Wallets → ⚙) keeps its nostr address card for gift-locking. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Header: wallet + avatar only; the avatar opens an identity menuAdam Soltys2026-08-021-17/+3
| | | | | | | | | | | The chat button leaves the header — the avatar's popup menu carries Edit profile, Settings, Messages, and Logout (lock()). The menu is available inside the chat takeover too, which now keeps the full header. Messages provides just the avatar node via the headerAvatar seam; the app owns the menu. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Profile: copy icon inside the npub box instead of a "Copy key" buttonAdam Soltys2026-08-021-3/+8
| | | | | | | | An npub isn't a key — no button, no label; just a small copy glyph right-aligned in the box. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Identity polish: 12-char npub default names, no ellipsis, copyable npubAdam Soltys2026-08-022-4/+5
| | | | | | | | | The registrar's auto-claimed username and every npub-fallback display name use the npub's first 12 characters, shown whole (no truncation dots), and the profile page's npub gets a copy button. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Own profile opens straight into the editor; avatar rightmost in headerAdam Soltys2026-08-021-36/+39
| | | | | | | | | | The Edit profile button is gone — your profile page renders the prefilled kind-0 form as soon as the published profile loads, with one Save. The header order becomes chat · wallet selector · avatar, via a dedicated headerAvatar seam so the avatar sits rightmost. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Punk default avatars, header profile button, owner channel creationAdam Soltys2026-08-021-15/+48
| | | | | | | | | | | | | | | | Everyone gets a face: profiles without a picture fall back to a coinos punk, picked deterministically by the pubkey's last byte — the same 64 webps and formula the coinos app uses (initials show through if the image can't load). Your own avatar moves from the chat home to the app header next to the wallet selector, opening your editable profile from anywhere. Community owners can add channels from the room's + panel: one owner-signed ChannelMetadata edition (CORD-03), and every member's channel switcher grows when the fold lands. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* History detail: show the zap recipient chip there tooAdam Soltys2026-08-021-0/+9
| | | | | Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Profiles: view pages, editable own kind 0, zap recipients in historyAdam Soltys2026-08-024-11/+172
| | | | | | | | | | | | | | | | | | | Tapping any avatar or author name (chat rooms, DM threads, the chat home) opens a profile page: avatar, name, nip05, lightning address, about, npub, with Message and Pay actions (Pay runs the npub payment cascade). Your own profile — reachable from your avatar on the chat home — gets an editor for name, about, picture, and lightning address (prefilled with your coinos address); saving merges over the newest published kind 0 so fields we don't understand survive the round trip. Zaps now remember who they went to: the ark feature keys the recipient by what the movement already records (destination address or invoice) and history rows wear a clickable avatar chip that opens the profile. The messages feature exposes showProfile/profileChip seams so any feature can do the same. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Pay an npub: ark > lightning > on-chain, and fix the orphaned LN seamsAdam Soltys2026-08-021-6/+67
| | | | | | | | | | | | | | | | | | | Picking a search result (or pasting an npub) now resolves every way the person can be paid and takes the best: their kind-10037 ark advert; an ark= instruction in BIP-353 via their coinos name (registrar /pubkey returns the URI directly) or their nip05 name's own DNS record (a bare domain is the BIP-353 "_" user); then Lightning from profile lud16/ lud06 through the zap flow; then a BIP-353 on-chain address as last resort. Search results no longer put the nip05 identifier in the fill value — that's how fiatjaf.com ended up as a dead recipient. Along the way: the zaps feature's canLnPay/startLnPay seams had no provider since the swaps feature retired, so the whole Lightning-zap path was unreachable. Ark provides both now (the ASP pays invoices natively), which also revives zapping any LN-only npub. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Recipient search: usernames and npubs, in chat and the Send formAdam Soltys2026-08-021-18/+46
| | | | | | | | | | | | | | | | | | | | A shared engine (src/recipient-search.js) merges three ranked sources: an exact npub/hex paste, coinos usernames from the registrar's new /search prefix endpoint, and NIP-50 kind-0 full-text search on relay.nostr.band — the approach the major web clients take, since ordinary relays don't index profiles. Results dedupe by pubkey, fill avatars in one batched kind-0 fetch, and cache per query so narrowing never re-hits the network. Input is debounced 300ms and gated by a searchable() heuristic so addresses, invoices, and keys never trigger a search. Chat's New message panel narrows as you type and opens the thread on pick; the Send form shows the same candidates under the destination input and picking one runs the normal paste path (payment address → BIP-353, npub → zap). Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Chat home: New message / Join / Create as real buttonsAdam Soltys2026-08-021-3/+3
| | | | | | | | They rendered as linklike text, indistinguishable from the section headings beside them; btn-sm gives them borders and weight. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* One global account: you're in Spending or Savings everywhereAdam Soltys2026-08-021-9/+5
| | | | | | | | | | | | | | | The balance card's label becomes a Spending/Savings toggle and the selection is app-wide state (persisted per device): the headline shows the selected balance with the other quiet below, Receive shows the payment address in Spending and a fresh on-chain address in Savings (the per-page dropdown and its iconSelect widget are gone), and a gift's source follows the account instead of its own toggle. Wallets without Ark see the old single-balance card. Send stays destination-driven — an invoice pays from Spending, an address from Savings, as before. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Receive: two options — Spending (payment address) and Savings (on-chain)Adam Soltys2026-08-021-78/+0
| | | | | | | | | | | | The Instant mode is gone: its raw ark address and in-app invoice generator are covered by the payment address, which already receives Ark and Lightning through the registrar. The dropdown now reads Spending / Savings, matching the balance card's language. Dead pane code (arkReceivePane, arkLnReceiveSection) removed with it; LN receives via NWC and the registrar are untouched. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Ark: partial moves to Savings via an exact-amount splitAdam Soltys2026-08-021-7/+52
| | | | | | | | | | | | | | | Into Savings now has an amount field with a Max button. Max (or the full balance) keeps the existing whole-balance cooperative offboard. A partial amount first mints a vtxo of exactly that amount with a self-send — the mailbox delivers self-sends, verified on regtest — then offboards just that vtxo, so the number typed is the number that leaves Spending (fees still come off what arrives, shown on the success screen). startOffboard accepts a vtxo subset for this; an exact-match vtxo skips the split. The unilateral exit page remains reachable from a link under the field. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Chat behind a header button, full-screen; move-panel polishAdam Soltys2026-08-022-7/+22
| | | | | | | | | | | | | | | Chat leaves the tab bar (which returns to its four evenly spaced tabs) for an icon button in the header next to the wallet selector. It now takes over the whole screen — no balance card, no tabs — with a back button on the chat home view, and the logo still leads back to the wallet. The generic tabs/tabContent seams this reverts to unused are removed again. Move panel: the direction toggle stretches across the card, and the "X sats in Spending" line is gone — the balance sits right above it. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Ark: automatic vtxo upkeep, and Move money inline on the balance cardAdam Soltys2026-08-021-36/+57
| | | | | | | | | | | | | | | | Refresh/consolidation is no longer a user concern: after each sync the wallet renews automatically when any spendable vtxo is within ~a week of expiry or fragmentation reaches six vtxos (a send needs one vtxo that covers the amount), throttled to one attempt per half hour and never while another round is in flight. The manual refresh button is gone. The separate Move money page is gone too — the button on the balance card now unfolds an inline panel with an Into Spending / Into Savings toggle: boarding happens right there, and Into Savings hands off to the existing exit page (cooperative offboard + unilateral exit unchanged). Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Settings: surface wallet settings, add Nostr section, tuck rarities into ↵Adam Soltys2026-08-024-32/+6
| | | | | | | | | | | | | | | | | | Advanced The main Settings tab now inlines the active wallet's own cards (recovery phrase, name, public key) instead of linking to a sub-page, followed by payment address, Ark status, network, and data source. All nostr cards — address, account link, wallet connect, device sync — sit under one Nostr heading via a new nostrSettingsCards seam. Offline transfer, address rescan, and auto-logout move to an Advanced sub-page. The Ark provider picker is gone: mainnet always rides coinos (a stale stored choice no longer wins); dev networks keep their per-network defaults. The per-account screen (Accounts → ⚙) reuses the same card builders unchanged. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Chat: device sync via Community List (13302) and Invite List (13303)Adam Soltys2026-08-021-4/+181
| | | | | | | | | | | | | | | Memberships and minted invite-link keys now follow the user across devices: both lists are NIP-44 self-encrypted replaceables (CORD-02 §8, CORD-05 §4), published under the wallet key (the cross-device constant) and the login npub when a signer is live. Merging validates the self-certifying community_id before adopting keys, keeps tombstones permanent, and lets the newest of added_at/removed_at win, so a stale device can never resurrect a community you left. Leaving is a two-tap control in the room's invite panel (the built-in coinos community is exempt) and publishes a guestbook Leave alongside the tombstone. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Chat v2: DMs, invites, and multi-community — coinos is the default, not ↵Adam Soltys2026-08-021-223/+709
| | | | | | | | | | | | | | | | | | | | | | | the only - NIP-17 private DMs (src/dm.js): kind-14 rumors in sender-signed seals under ephemeral wraps; interops with other nostr DM clients. Login signers gained encryptTo/decryptFrom so extensions and bunkers can seal to a peer; the inbox listens for both the login npub and the wallet key and publishes a kind-10050 DM-relay list for the wallet key when none exists. - Concord invites (CORD-05): mint shareable links (33301 bundle on relays, keys only in the URL fragment), join by pasting a link or opening v3.coinos.io/invite/…, and direct invites giftwrapped to an npub with the k-tag index hint. Bundles self-certify the founder before anything joins. - Multi-community: home view lists DM threads and communities; found your own community in-app (owner-signed genesis). coinos stays baked in as default. - Welcome DM: the names registrar DMs every first-time registrant from Adam's npub on claim (config: welcome{sk,message}; deduped in registrar state). Every new wallet auto-claims a payment address, so every new user gets one. - relay.coinos.io write policy now allows 33301/13302/13303. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Rebrand: hal → coinos v3Adam Soltys2026-08-022-8/+8
| | | | | | | | | | | | 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-022-1/+417
| | | | | | | | | | | | 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
* Nostr mark: brand gradient and room to breatheAdam Soltys2026-08-021-2/+2
| | | | Co-Authored-By: Claude Fable 5 <[email protected]>
* Use the real Nostr mark on the sign-in buttonAdam Soltys2026-08-021-2/+3
| | | | | | | The hand-rolled monochrome ostrich read as a smudge at 18px. This is the brand asset from coinos-ui, purple kept, at 22px. Co-Authored-By: Claude Fable 5 <[email protected]>
* Deliver incoming Lightning without depending on an operator floatAdam Soltys2026-08-021-2/+7
| | | | | | | | | | | | | | | | A payment to an address settled on our node and then sat in a retry queue because the float wallet that forwards it was empty — the money was safe but invisible to the recipient. Delivery now has a second route: ask the recipient's wallet for an invoice (CLINK) and pay it over Lightning, which needs no float at all. The float path stays first because it works while they're offline; the queue is now the last resort rather than the only one. Existing addresses never carried the wallet's offer key, so that route was unavailable to them. The wallet now records what it published and re-registers whenever the registrar is missing the current key. Co-Authored-By: Claude Fable 5 <[email protected]>
* Payment addresses accept Lightning from LNURL-only walletsAdam Soltys2026-08-012-0/+4
| | | | | | | | | | | | | | An address only worked for wallets that speak DNS payment instructions or BOLT 12; everything else looks for /.well-known/lnurlp/<name>. The registrar now serves LUD-06/16 (with NIP-57 zap support), preferring the path that touches no funds: if the recipient's wallet published a CLINK offer key, we ask IT for the invoice over nostr and hand the result straight back — the payment never passes through us. Only when nobody answers does our node mint the invoice, and the existing settle forwarder pushes the money on to their address, signing a zap receipt when the payment was a zap. Co-Authored-By: Claude Fable 5 <[email protected]>
* Speak about money, not rails: Spending and SavingAdam Soltys2026-08-011-43/+70
| | | | | | | | | | | | | | | | The balance card now leads with the total and names the two places money lives — Spending (instant) and Saving (on-chain) — instead of an on-chain headline with an 'Ark balance' line hanging off it. A 'Move money' button under the balance opens one screen that moves funds either way, replacing a board form buried inside the Ark receive pane. Strings throughout drop Ark and Lightning: boarding becomes 'Move to Spending', exiting becomes 'Move to Saving', the receive tabs read 'Instant' and 'Saving address', and How it works explains two balances and the escape hatch without naming a protocol. The rails keep their names in the code, where they belong. Co-Authored-By: Claude Fable 5 <[email protected]>
* Drop npub-locked gifts; collapse Nostr login behind a buttonAdam Soltys2026-08-013-165/+24
| | | | | | | | | | | | | | Gifts are plain bearer links again: the recipient-npub field, the one-time claim code, the DM delivery, the /lg/ claim route and the NIP-07 in-place decrypt are all gone, along with the crypto that only they used. Unclaimed gifts remain reclaimable by the sender, so nothing can be stranded by the removal. The unlock screen shows a single 'Sign in with Nostr' button that reveals the login options when tapped, instead of the full card competing with create/import. Co-Authored-By: Claude Fable 5 <[email protected]>
* Stop the Ark card flickering on the settings pageAdam Soltys2026-08-011-2/+15
| | | | | | | | | | | | Every synced snapshot re-ran initArk() while Ark was unconnected, and initArk() calls stopArk() first — so each arriving snapshot tore down and restarted the connection, flashing 'Connecting to the Ark server…' and jittering the page. Automatic re-inits are now skipped while a connection exists or is in flight, and rate-limited to one per 20s; user-driven reconnects (switching servers, the connect button) are unaffected. Co-Authored-By: Claude Fable 5 <[email protected]>
* Restore two helpers an earlier edit deleted, and guard the class of bugAdam Soltys2026-08-011-0/+14
| | | | | | | | | | | | | | A bulk replacement removed lookupMine() and the flag along with the block it was rewriting, so names setup threw at runtime — the kind of break every existing test happily passed over because nothing ever constructed a feature and started it. tools/features-smoke-test.js does exactly that: builds every feature against a stubbed wallet with the network cut, runs init/render/stop, and fails on undefined identifiers. It caught the second missing helper immediately. Co-Authored-By: Claude Fable 5 <[email protected]>
* Get an address immediately, without waiting on a signer promptAdam Soltys2026-08-012-25/+32
| | | | | | | | | | | | | | | The passive setup path asked the browser extension to sign the claim, so it sat behind a permission popup — 20s per attempt, retried, hence the minute-long spinner ending in the manual form. The wallet's own key now makes that claim: no signer, no prompt, no wait. Tying the address to the user's real npub happens at login instead, where the signer is already live, and nominates the wallet key to manage the record after. Failures are visible now rather than hidden: the pane shows why setup failed, the watchdog is 12s instead of 60s, internal timeouts are tighter, and the registrar logs every refusal with its reason. Co-Authored-By: Claude Fable 5 <[email protected]>
* The real cause: the CORS preflight rejected our own auth headerAdam Soltys2026-08-011-8/+21
| | | | | | | | | | | | | | | | NIP-98 puts the claim under an Authorization header, but the registrar only allowed content-type in access-control-allow-headers, so browsers blocked every claim before sending it. The failure arrived as a generic network error, which the retry logic treated as transient — hence the endless 'setting up your payment address' spinner. Bun tests never saw it because they don't preflight. Registrar now allows the header. Client-side, nothing may hang: signing, the registrar call and the ark lookup all have timeouts, retries are capped, and a one-minute watchdog always resolves the spinner into either an address or the manual form. Co-Authored-By: Claude Fable 5 <[email protected]>
* Never spin on a claim that can only failAdam Soltys2026-08-012-6/+36
| | | | | | | | | | | | After a reload the extension signer is gone, so claiming the user's real npub name was signed by the wallet key — which the anti-squatting rule rightly refuses, and the retry loop then span forever. An installed extension is now silently re-attached when possible (keeping the address tied to the real identity across reloads), refusals are treated as permanent rather than retried, and anything unresumable falls back to the wallet's own npub so an address always appears. Co-Authored-By: Claude Fable 5 <[email protected]>
* Actually assign the default payment addressAdam Soltys2026-08-011-3/+22
| | | | | | | | | | The auto-claim ran once at feature init — before Ark finishes connecting — saw an unavailable wallet and gave up for good, so users got a claim box instead of their npub address. It now retries while Ark comes up (and after a failed claim), and shows a setting-up state rather than the claim form while it works. Co-Authored-By: Claude Fable 5 <[email protected]>
* One wallet per Nostr identity, NIP-98 auth, npub payment addressesAdam Soltys2026-08-013-48/+65
| | | | | | | | | | | | | | | | | | | | | | | | | | 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-012-1/+165
| | | | | | | | | | | | | | | | | | | | | | | | | 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]>
* Default usernames from the npub — no onboarding promptAdam Soltys2026-08-011-14/+23
| | | | | | | | | | | | | Every wallet silently claims the first 20 characters of its npub as its payment address the first time it opens: unique, free, and nothing to decide. The pick-a-username gate is gone (the generic onboardingView hook stays as an extension point). A custom name is an optional change tucked into the payment-address card — and swapping to one releases the old name so the registrar and DNS stay tidy. Custom names may become paid later to deter squatting; the default tier keeps working regardless. Co-Authored-By: Claude Fable 5 <[email protected]>
* Username onboarding + the payment address leads ReceiveAdam Soltys2026-08-011-23/+85
| | | | | | | | | | | | | | | New wallets must pick a username before entering the wallet: the onboarding gate renders a claim screen until a name exists. Imported seeds skip it — the registrar's new /pubkey lookup recovers the name owned by the wallet's key, so nothing is retyped. If claiming errors (registrar or ark unreachable) a do-it-later escape appears rather than locking anyone away from funds; the prompt returns next open. The Receive tab now defaults to the payment address — name QR + copy — with the on-chain address and other modes behind it, and the claim form inline for wallets that skipped onboarding. Co-Authored-By: Claude Fable 5 <[email protected]>
* Fresh-seed restore: connect after parking a synced ark snapshotAdam Soltys2026-08-011-0/+6
| | | | | | | | | | | | A seed imported on a new device (or a new origin — v3.coinos.io) has no local ark state and no live connection, so the anti-poisoning guard can't prove the incoming snapshot's owner and parks it in the hold. Nothing then triggered the connect that would adopt it — ark is lazy on fresh wallets — so the balance never appeared. Parking with no local identity now kicks the init; connecting learns the server pubkey and the held state is adopted iff it matches. Co-Authored-By: Claude Fable 5 <[email protected]>
* Coinos branding: logo and black & white themeAdam Soltys2026-08-011-1/+1
| | | | | | | | | 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-012-9/+9
| | | | | | | | | | | | | | | 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]>
* Zappable offer codes and per-name Lightning offersAdam Soltys2026-08-013-14/+151
| | | | | | | | | | | | | | | | | | | | | | | | | CLINK offers (noffer): every wallet gets a static offer keypair (synced across devices, first-minted wins) answering kind-21001 invoice requests — from open tabs and from the service worker while closed, which mints against the mirrored state (the record now carries chain-4 and chain-5 key windows) and raises an open-to-receive notification. Zap requests ride along and their kind-9735 receipts publish once the payment is claimed. The code is shown in the payment-address card for pasting into a Nostr profile; the notifier watches and relays kind 21001 alongside NWC. Payment names also gain a Lightning offer: the registrar creates a BOLT 12 offer per name on the node and appends lno= to the DNS record; a settle-forwarder pushes each received payment on to the name's ark destination from a small float wallet (custody window: seconds between Lightning settle and the ark send). Name resolution now runs full client-side DNSSEC validation (src/dnssec.js): binary DoH, root-anchor chain walk, RSA + ECDSA via WebCrypto — the resolver is untrusted transport, verified against live signed zones including the canonical BIP-353 test name. Co-Authored-By: Claude Fable 5 <[email protected]>
* Payment names: ₿[email protected] via BIP-353 DNS payment instructionsAdam Soltys2026-08-014-1/+197
| | | | | | | | | | | | | | | | | | | | 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-016-1211/+117
| | | | | | | | | | | | | | | | | | | | | | | | | 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-012-11/+185
| | | | | | | | | | | | | | | | | | | | | | | 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]>
* Refresh the push-notifier registration whenever connections changeAdam Soltys2026-08-011-1/+29
| | | | | | | | | | The notifier only wakes a device for service pubkeys it was told about at registration time, so recreating a connection silently killed background wake-ups — the registration kept watching the revoked key forever. Re-register (debounced; the server replaces by endpoint) on init, create, revoke, and sync-merge whenever background alerts are on. Co-Authored-By: Claude Fable 5 <[email protected]>
* Split device sync into per-domain slots with bounded, dirty-only publishesAdam Soltys2026-08-013-14/+65
| | | | | | | | | | | | | | | | One monolithic snapshot per device outgrew a vanilla relay's 64KB event cap the moment real payment traffic arrived, and sync silently died. Snapshots now fragment into per-domain replaceable events (core wallet, ark, nwc — an extension claims a domain by naming it), published under a new ':x:' d-tag marker that pre-split clients don't match, so they can never mistake a fragment for a full snapshot. Only domains whose content changed republish, a refused publish retries on the next save instead of being marked done, and readers apply full-snapshot semantics only to core-carrying events. The ark projection also caps its history: spent stubs at 200, done actions at 50 — the event size is bounded for life, no special relay rules required. Co-Authored-By: Claude Fable 5 <[email protected]>
* Keep sync snapshots under relay size limits, and say when they bounceAdam Soltys2026-08-011-1/+4
| | | | | | | | | | | | Tonight's payments pushed the encrypted snapshot past strfry's 64KB maxEventSize and every publish after the first zap was silently refused — the other device just showed no history, with nothing logged anywhere. The relay's limit is raised to 256KB; client-side, movements older than the last 50 now drop their bolt11/preimage (~half a KB per ln-send) and the cap tightens to 200, and a refused snapshot publish logs the relay's reason. Co-Authored-By: Claude Fable 5 <[email protected]>