aboutsummaryrefslogtreecommitdiffstats
path: root/names/server.js
Commit message (Collapse)AuthorAgeFilesLines
* names: a migration grant unlocks a reserved legacy name.Adam Soltys2026-09-101-7/+10
| | | | | | | | | | | | | Migrating the coinos.io house account `coinos` swept its balance fine, then every claim bounced with "name is reserved": the reserved-word check ran before the migration grant was consulted. A reserved word that is also a real coinos.io account can only reach the registrar through a migration, whose grant is the old account's authenticated say-so — so the grant now clears the reserved check too. Reserved names without a grant stay reserved. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01Nw1Dijik1tLVKRM1WkU7xE
* Payment push notifications are scoped to their network, so a mainnet payment ↵Adam Soltys2026-09-051-1/+3
| | | | | | | stops opening the staging PWA. The same seed derives the same nostr pubkey on mainnet and staging, so both PWAs registered push under one pubkey and the notifier — matching only by pubkey — fanned every mainnet payment out to the staging subscription too; tapping it opened staging.coinos.io. Now the client stamps its network on the push registration (pushWatch net: getNetwork()), the registrar declares its own network on the /notify call (CFG.forwarder.network), and nwcpush delivers a payment only to registrations whose net matches — with a rollout-safe fallback: a device that hasn't re-registered yet (net=null) still gets the push, and a caller that omits net still reaches everyone, so nobody goes dark mid-deploy. Deployed names + nwcpush on cs Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01MGzU12QrAA4G6BYGzGVdXz
* Point of sale: a coinos-pos terminal works against a nameAdam Soltys2026-09-051-2/+209
| | | | | | | | | | | | | | | | | | Registrar: the owner mints a bearer POS token from Settings (NIP-98 POST /pos/token, DELETE revokes; /admin/pos/token for operators). With it the terminal gets /pos/me, rings up sales at /pos/invoice (fiat via coinos.io rates, or sats), reads /pos/payments, and holds a websocket at /pos/ws that gets a payment message when the sale settles. Sales are always minted on our node so the settle loop sees them and the ordinary forward path delivers the sats to the name's ark address. The name's lnurlp pins the newest unpaid sale under five minutes old — coinos.io's /p/<name> rule — so an NFC tap or a scan pays exactly that invoice. Wallet: a "Point of sale" section on the names card creates, copies and revokes tokens. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01VMi2Y1oKPZ3TeeFmLej47y
* Lightning receives to a [email protected] address now wear the ⚡ instead of ↵Adam Soltys2026-09-041-4/+39
| | | | | | | the Ark mark: a faucet paying the LN address settles on the registrar's CLN and arrives as an anonymous arkoor from the float (the mailbox protocol carries no memo), so the wallet genuinely couldn't tell the rail. The registrar now records a receipt {sat, hash, ts} per LN settlement it forwards via the float (7-day ring per name) and serves them at GET /lnreceipts (NIP-98, owner/manager only — it's a feed of incoming amounts); the wallet fetches them through the names feature (which holds the signer) and fuzzy-matches receive movements by exact amount + delivery within ±3 min, stamping viaLn + the payment hash so a receipt can never claim a second row. History row and detail view show ⚡ for viaLn receives; the tag rides the sync snapshot to other devices via merge enrichment. viaClink deliveries need none of this — the wallet's own LN-receive flow already tags itself. Best-effort by design: an amount+time collision only mislabels an icon Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01MGzU12QrAA4G6BYGzGVdXz
* The registrar grows a webhook API — the coinos.io-style merchant notify, ↵Adam Soltys2026-09-031-0/+166
| | | | | | | self-custodial edition: a name's owner registers an https URL (NIP-98, owner or manager key) and every Lightning settlement for that name POSTs {type, address, amount, hash, preimage, memo, secret} there, firing on settlement independent of the ark delivery. URLs are vetted hard (https only, no credentials, every resolved address must be public unicast — RFC1918/CGNAT/loopback/link-local refused, re-checked before every delivery), retries back off 30s/5m/30m from a persisted queue, and GET /webhook shows the config plus the last delivery's outcome. Proven live: a throwaway name, a 21-sat Lightning payment, one webhook delivered Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01MrkLsNScXe9b6BviUSdmMr
* The tester with 233 sats gets to spend them: invoice coverage judges the SUM ↵Adam Soltys2026-09-021-0/+33
| | | | | | | of spendable coins (the single-coin gate silently declined anything over the largest coin, though multi-input pays gather 24), an uncoverable invoice now owns its paste — board the shortfall from Savings, or say what's short — instead of dropping the bolt11 into the address field like a dead click, and chain calls gain a last-resort relay through the registrar (/esplora passthrough, tip cached) for the network that blackholes every public esplora Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01MrkLsNScXe9b6BviUSdmMr
* A hard hat for the crew that builds this with us: an operator-giftable ↵Adam Soltys2026-08-311-3/+28
| | | | | | | construction hat joins the catalog (never listed for sale — like the crown it appears only in a rack that holds it), the registrar grows POST /hats/grant behind the admin token (hex or npub, crown excluded), and a gifted hat settles into the rack instead of knocking a paid hat off a bare-headed recipient wears it at once Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01MrkLsNScXe9b6BviUSdmMr
* Google accounts can speak: the DM seal signs with the present for remote ↵Adam Soltys2026-08-301-4/+10
| | | | | | | signers (the pomegranate FROST operators refuse backdated events, and NIP-59's tweak on the seal protects nothing — only the recipient ever sees that layer; the public-facing wrap keeps its tweak under our own ephemeral key), and the registrar welcomes each PERSON once instead of once per key — the Google flow's two claims (wallet key, then real npub with the wallet as manager) were earning the same inbox two identical greetings Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01MrkLsNScXe9b6BviUSdmMr
* The registrar learns who a migrated name belongs to: coinos.io's migrate ↵Adam Soltys2026-08-301-1/+38
| | | | | | | action — running with the old account's authenticated session, which IS the ownership proof — posts a migrate-grant (Bearer migrateToken) naming the v3 destination, and the claim guard honors that grant where the identity-only check (old account's npub must sign the claim) could never pass for a v3 wallet key; without it every migrated claim 409'd forever and the wizard spun on 'Claiming…' Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01MrkLsNScXe9b6BviUSdmMr
* Staging gets the mainnet fee model end to end: /lnquote?network=mutinynet ↵Adam Soltys2026-08-291-12/+16
| | | | | | | quotes from the staging ASP's own CLN (helpers take their backend), the client asks it by default on mutinynet, and the quote URL concat survives a query string Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_019WboeoetMF3MTmgT7Ew8yi
* The wallet learns to read BOLT 12 for itself: offers and invoices decode ↵Adam Soltys2026-08-291-8/+49
| | | | | | | client-side, and every invoice the ASP fetches over onion messages is verified here — payment hash read from the raw TLVs, offer terms mirrored byte-for-byte, the issuer's schnorr signature checked against LDK's exact merkle construction (proven against a live CLN-signed fixture) — so no server can hand back an invoice whose preimage it already holds. Paste an lno1 or resolve a name whose record carries one and the existing HTLC pay flow carries it (captaind's Invoice enum takes lni strings natively); a name's owner may now publish their OWN node's offer in their DNS record, retiring the registrar's stand-in — senders fetch invoices straight from their node and no coinos service ever touches the money. The /lnquote fallback prices blinded paths by entry node + advertised payinfo Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_019WboeoetMF3MTmgT7Ew8yi
* An address can point home: a name's owner may register their own CLINK offer ↵Adam Soltys2026-08-291-25/+79
| | | | | | | code and the registrar asks THEIR node for invoices — delivered there first, float as fallback, and no invoice is relayed or paid unless it demands exactly the sats owed on the right network. hal learns to pay pasted noffer1 codes through the zap flow (fixed, variable and payer-chosen pricing, amount verified before the HTLC locks). The depth advisory slims to one line — an uncooperative exit's price and a Manage link — and the new coins screen shows each vtxo's amount, expiry and arkoor depth beside what renewal costs and buys Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_019WboeoetMF3MTmgT7Ew8yi
* A name the registrar no longer grants is a costume: the wallet verifies its ↵Adam Soltys2026-08-281-1/+1
| | | | | | | cached name against the record's owner and manager keys each refresh, shedding stale ones (an evicted squat once lingered in the squatter's UI) — the /name endpoint now names the manager too Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_019eA8JRq9Wr7z2fRQk99HLi
* A name's proof is its key, not a flag: takenByCoinosUser stops trusting ↵Adam Soltys2026-08-281-5/+8
| | | | | | | `migrated` (which let poppy get squatted) — an existing coinos.io name yields only to a claim signed by that account's own nostr key; the squatted record, its DNS entry and its offer are evicted Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_019eA8JRq9Wr7z2fRQk99HLi
* The chosen name outranks the placeholder everywhere: a claim can no longer ↵Adam Soltys2026-08-271-1/+7
| | | | | | | release a custom name to make room for an npub default (the race that deleted passkeyguy), sign-in asks the registrar about the login identity before reaching for a placeholder, and the registrar answers /pubkey with the name the user picked rather than whichever record is younger Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_019eA8JRq9Wr7z2fRQk99HLi
* Dead coins stop travelling: an expired vtxo leaves the balance and the coin ↵Adam Soltys2026-08-241-0/+26
| | | | | | | picker (sub-dust IOUs excepted), a shortfall names the dead weight it can't spend, and the names float renews any coin a day ahead of the reaper — a 100k forward left home on an expired coin and the recipient rightly refused it at the door Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_019eA8JRq9Wr7z2fRQk99HLi
* The float sips instead of gulping: top-ups arrive in pool-sized chunks that ↵Adam Soltys2026-08-181-2/+5
| | | | | | | accumulate to the target, sparing the ask that dies whole on a thin vtxo pool Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_012u4oe1KPx9faCsDBLfZYqi
* The float keeps itself fed: below low water it loops sats off the CLN ↵Adam Soltys2026-08-181-4/+106
| | | | | | | through a neighbourly circular route all by itself, the sync heartbeat drives whatever's mid-flight every minute, and an operator can board on-chain reserves in through two admin calls Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_012u4oe1KPx9faCsDBLfZYqi
* The party hat leaves the party: gone from catalog and price list alike — ↵Adam Soltys2026-08-181-1/+1
| | | | | | | heads that bought one on staging simply go bare Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01JtaatFfAbx91Nh3xqKVqKz
* A wallet keeps one name per domain, not one name total: staging and mainnet ↵Adam Soltys2026-08-181-1/+4
| | | | | | | claims stop clobbering each other, the registrar answers who-am-I per domain, and a custom name claimed on one device replaces the npub placeholder on every other Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01JtaatFfAbx91Nh3xqKVqKz
* Staging LNURL declines what it cannot deliver: only the recipient wallet ↵Adam Soltys2026-08-171-0/+5
| | | | | | | mints for @staging names — our mainnet node will not take real sats it can only strand Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01JtaatFfAbx91Nh3xqKVqKz
* Staging gets a hat economy of its own: mutinynet mints on its own CLN into ↵Adam Soltys2026-08-171-25/+39
| | | | | | | its own registry, play sats never buy mainnet flex — plus the toque earns its Canadian name, the wizard takes the throne at 2.1M, the crown leaves the menu, and every hat sits a little higher and straighter 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-0/+136
| | | | | | | 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
* Every name learns to vouch for its keeper: the registrar answers nostr.json, ↵Adam Soltys2026-08-151-0/+11
| | | | | | | so [email protected] is a NIP-05 too Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_014tE4U5nEjGTQy8PwYqamfv
* Welcome DMs stop echoing: sent-copies thread under their recipient whichever ↵Adam Soltys2026-08-151-3/+4
| | | | | | | identity authored them, the registrar keeps no sender copy at all, and the old self-thread residue sweeps itself Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_014tE4U5nEjGTQy8PwYqamfv
* A claim with an ark address that doesn't decode is refused at the door — a ↵Adam Soltys2026-08-151-0/+9
| | | | | | | corrupt one recorded is a payment black hole Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_014tE4U5nEjGTQy8PwYqamfv
* A failed forward retry says why — the goyslop 190k spent a day failing in ↵Adam Soltys2026-08-151-1/+2
| | | | | | | a bare catch Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_014tE4U5nEjGTQy8PwYqamfv
* An invoice's name mapping lives as long as the invoice can settle, and a ↵Adam Soltys2026-08-151-2/+13
| | | | | | | nameless settle leaves a trace — one paid 1000 sats vanished into a silent continue behind a 1-hour sweep Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_014tE4U5nEjGTQy8PwYqamfv
* Sub-sat routes round back up: a zero quote just fails its first attempt and ↵Adam Soltys2026-08-141-5/+7
| | | | | | | pays 1 sat on retry anyway — true free needs the ASP to bring the msats Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_014tE4U5nEjGTQy8PwYqamfv
* A route that costs less than a sat quotes as free — the ASP eats the ↵Adam Soltys2026-08-141-2/+8
| | | | | | | msats, which mostly land back on our own node Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_014tE4U5nEjGTQy8PwYqamfv
* The network fee gets asked, not assumed: /lnquote prices a lightning payment ↵Adam Soltys2026-08-131-0/+77
| | | | | | | through askrene before anything locks — zero to ourselves and direct peers Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_014tE4U5nEjGTQy8PwYqamfv
* The float reads its mail: sync at startup and before each retry round, so a ↵Adam Soltys2026-08-091-0/+6
| | | | | | | top-up is seen without a restart Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01DbA53PJA8fgSvMcEzfuw22
* A stuck forward now holds its tongue: retries back off to an hour, and an ↵Adam Soltys2026-08-091-2/+11
| | | | | | | offer knock reads as money arriving, not leaving Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01DbA53PJA8fgSvMcEzfuw22
* Registrar: a migrated legacy name is claimableAdam Soltys2026-08-031-1/+5
| | | | | | | | | takenByCoinosUser now reads the account it finds: a live one still blocks the name, but one flagged migrated has handed receiving over, so v3 may claim it. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* BOLT 12 offers with a custom memo on the Spending receive pageAdam Soltys2026-08-031-0/+41
| | | | | | | | | | | | | | | | | | Mining pools (Ocean) and payroll pay to a reusable offer rather than a lightning address, and the memo is how the payer labels it. A disclosure under the payment address mints one: the registrar's new NIP-98'd /offer endpoint asks the ASP's CLN for an `amount: any` offer carrying the memo, keyed to the caller's name (issuer = their address, so two users' identical memos can't collapse onto one CLN offer). Up to ten each, identical memos reuse the existing offer, and GET lists them. Settled offer payments already had a home: the registrar's settle loop now resolves a custom offer id to its owner and hands it to the same forwarder the default offer uses — an instant, free arkoor send into the user's Spending balance. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Push notifications for payments and messages; offline Spending noticeAdam Soltys2026-08-021-0/+7
| | | | | | | | | | | | | | | | | | | | | | | 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
* Recipient search: usernames and npubs, in chat and the Send formAdam Soltys2026-08-021-0/+14
| | | | | | | | | | | | | | | | | | | | 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 v2: DMs, invites, and multi-community — coinos is the default, not ↵Adam Soltys2026-08-021-0/+44
| | | | | | | | | | | | | | | | | | | | | | | 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
* Registrar: an operator endpoint to top the float up over LightningAdam Soltys2026-08-021-0/+31
| | | | | | | | The float pays out in Ark while incoming Lightning accumulates on the node, so it needs periodic refilling. It can now mint an invoice against its own balance (token-guarded, since the endpoint is public). Co-Authored-By: Claude Fable 5 <[email protected]>
* Deliver incoming Lightning without depending on an operator floatAdam Soltys2026-08-021-5/+23
| | | | | | | | | | | | | | | | 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-011-2/+155
| | | | | | | | | | | | | | 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]>
* Get an address immediately, without waiting on a signer promptAdam Soltys2026-08-011-2/+5
| | | | | | | | | | | | | | | 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-1/+1
| | | | | | | | | | | | | | | | 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]>
* One wallet per Nostr identity, NIP-98 auth, npub payment addressesAdam Soltys2026-08-011-24/+58
| | | | | | | | | | | | | | | | | | | | | | | | | | 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]>
* Username onboarding + the payment address leads ReceiveAdam Soltys2026-08-011-0/+13
| | | | | | | | | | | | | | | 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]>
* Coinos v3: rebrand from Hal, payment addresses move to @coinos.ioAdam Soltys2026-08-011-25/+48
| | | | | | | | | | | | | | | 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-011-6/+119
| | | | | | | | | | | | | | | | | | | | | | | | | 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-011-0/+189
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]>