aboutsummaryrefslogtreecommitdiffstats
path: root/src/nostr-login.js
Commit message (Collapse)AuthorAgeFilesLines
* The standalone wallet opened from a disk shows its avatars, font and languages.Adam Soltys13 days1-1/+2
| | | | | | | | | | 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
* Copy: no more em dashes, anywhere people read.Adam Soltys2026-09-271-2/+2
| | | | | | | | | Every English string, the translations, the README and the messages the code shows people now break with a full stop, a colon or a comma instead. Only console logs and the "—" placeholder for an empty cell keep the dash. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01VhUKyXKtsJ67QAFfb7QR9D
* Amber (NIP-55) signs you in on Android: one intent per request, the answer ↵Adam Soltys2026-09-271-0/+130
| | | | | | | | | | | | | | | read off the clipboard or pasted. Closes #7. A "Sign in with Amber" door on Android opens nostrsigner: intents for get_public_key, sign_event and nip44 encrypt/decrypt, with a waiting card that reads the clipboard when the tab returns (or takes a paste). Requests queue one at a time — a sign-in fires the backup, relay list and follows together — and each answer is checked for its type so a stale clipboard is never mistaken for one. An interactive signer is not asked to decrypt DMs or private lists that arrive on their own; each trip through Amber is a deliberate one. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01VhUKyXKtsJ67QAFfb7QR9D
* A remote signer that drops its socket comes back by itself.Adam Soltys2026-09-141-2/+16
| | | | | | | | | | | | | | | | "My signer keeps disconnecting": a phone freezing the tab cut the relay socket the bunker session talks through, every request after that hung to its deadline, and the app declared the signer gone — a Reconnect button, tapped over and over. Three changes: the bunker client now talks through a pool that pings and reconnects on its own, so a dropped socket is re-opened and the subscription re-fired without anyone noticing; a request that does fail on a dead connection drops the session and dials again silently, so the notice only appears if that resume fails; and coming back from the background probes the signer with a quick question, replacing a dead session before the next real request runs into it. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01QYJfZ4A1TDCEY4Foq3CApR
* The Amethyst handshake survives its own launch: opening the signer app ↵Adam Soltys2026-09-021-10/+104
| | | | | | | freezes the tab (relay sockets die as the approval publishes) and its callbackUrl then RELOADS the page, discarding the client key mid-handshake — so the pending connect now persists to sessionStorage, the wait races the live subscription against a stored-event poller kicked on every return to visibility, and a fresh boot resumes the handshake instead of shrugging. Proven both ways with a scripted signer: approval after reload, and approval stored while the page was gone Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01MrkLsNScXe9b6BviUSdmMr
* The DM drain wakes the signer itself: boot's single resume attempt losing to ↵Adam Soltys2026-08-301-1/+1
| | | | | | | cold-start churn left the phone holding every wrap with nothing ever asking the bunker again — now a drain that finds its decryptor set incomplete calls resume (single-flight, rate-limited) before deciding anything, and the bunker resume deadline stretches to 15s Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01MrkLsNScXe9b6BviUSdmMr
* The Amethyst signer stops dying every time a stranger's wrap fails to ↵Adam Soltys2026-08-301-5/+12
| | | | | | | decrypt: only connection-shaped failures (with a real per-op deadline now — a backgrounded signer app used to just hang the call) drop the live bunker, the reconnect surfaces offer only the door the account actually came through (a recorded 'via', so an Amethyst-linked npub no longer stares at Google and passkey buttons), the settings card quietly reattaches from the stored session instead of waiting for a tap, and the DM retry drain eases to three full-strength passes per wrap Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01MrkLsNScXe9b6BviUSdmMr
* The wallet remembers it has a spending side: the relay backup carries the ↵Adam Soltys2026-08-281-3/+6
| | | | | | | hint, stamped the moment spending becomes real, so a restore paints Spending from its first frame instead of reflowing when the name lookup catches up — and the sign-in toasts are gone, since a wallet appearing announces itself 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-8/+0
| | | | | | | 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-2/+21
| | | | | | | 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
* Never mistake a failed backup lookup for 'no wallet yet'Adam Soltys2026-08-041-6/+20
| | | | | | | | | | | | Signing in with a key whose wallet backup exists on relays could still land in 'create a new wallet' when the lookup failed (relays unreachable, signer decrypt denied) — and confirming would have replaced the real backup on any relay the new one reached. Absence is now only believed when a relay actually answered; decrypt failures surface as errors; and the create path takes one last look and adopts a backup that surfaces rather than clobbering it. Co-Authored-By: Claude Fable 5 <[email protected]>
* Speak as the account you logged in with, or not at allAdam Soltys2026-08-031-3/+28
| | | | | | | | | | | | | | | | | | | | | | | | | | Logging in with a remote signer and reloading left the app showing your avatar and profile while signing everything with the wallet's own key — so chat messages arrived under a different npub with a stranger's punk on them. identity() had a fallback: when the login signer wasn't available it quietly used the wallet key instead. That fallback is gone. If you logged in as a nostr account, the app speaks as that account or it refuses and says so. Refusing is only reasonable if the signer usually comes back, so a login now persists what it takes to be that identity again. A remote signer stores its NIP-46 client key and pointer — the signer authorised that client key, not one page load, so rebuilding from it resumes the session without prompting. A pasted key stores the key, which is already the seed of everything here since the wallet's mnemonic derives from it. Resume is single-flight with a pause after failure, because identity() is asked on every send and a signer that isn't answering must not mean a relay connection per keystroke. Existing remote-signer logins predate the stored session, so they need one more sign-in to become resumable. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Tab carousel, login avatar fix, signer callback hintAdam Soltys2026-08-021-1/+1
| | | | | | | | | | | | | | | | | | | | | | Tab changes no longer flash the whole page: only the content pane below the tab strip slides sideways with a soft cross-fade — swipe direction when a gesture caused it, index order for taps — while the balance card and strip stay planted. Full-page entrances remain for real page changes (settings, chat, profile, takeovers). After a nostr sign-in the header briefly wore the wallet key's face: nostrLoginIdentity now reports a just-connected live signer immediately instead of waiting for the persisted link that only lands after the wallet opens. The nostrconnect URI carries a callbackUrl hint (NIP-55 style) so signer apps that honor it can bounce back to the PWA after approval — apps that don't will still find the session signed in when the user returns, since the handshake completes over the relay in the background. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Nostr login: connect a signer app by deep link or QR (nostrconnect://)Adam Soltys2026-08-021-1/+32
| | | | | | | | | | | | | | The client-initiated NIP-46 flow: coinos mints a nostrconnect:// URI — rendered as a tappable deep link (launches Amber & friends directly, no bunker URL to copy) and a QR for cross-device scanning — then waits on the relay; when the signer app answers with the secret, login continues by itself. One live attempt at a time, cancellable, three-minute timeout, and kind 24133 joins the relay whitelist so our own relay carries the handshake. The bunker:// paste path stays for signers that prefer it. 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-0/+7
| | | | | | | | | | | | | | | | | | | | | | | 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
* Fix bunker (NIP-46) login: the pointer goes through fromBunkerAdam Soltys2026-08-011-1/+4
| | | | | | | | | | | nostr-tools' BunkerSigner constructor takes (clientSecretKey, params) — passing the bunker pointer as the second argument left the signer with no pointer at all, so connecting died on "reading 'pubkey'" before any network call. Use the static fromBunker, and reject an address that lists no relays with something a human can act on. Covered by a test using a real Amethyst-issued address. Co-Authored-By: Claude Fable 5 <[email protected]>
* One wallet per Nostr identity, NIP-98 auth, npub payment addressesAdam Soltys2026-08-011-4/+24
| | | | | | | | | | | | | | | | | | | | | | | | | | 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-0/+161
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]>