aboutsummaryrefslogtreecommitdiffstats
path: root/src/features/names.js
Commit message (Collapse)AuthorAgeFilesLines
* A door where people will look for it: 'Deliver to your own node' gets its ↵Adam Soltys2026-08-291-4/+11
| | | | | | | own entry in the address's more-options menu and a section in the Settings names card — it no longer hides as an appendix behind the CLINK zap code, which returns to being just that 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-39/+62
| | | | | | | 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-6/+60
| | | | | | | 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-0/+15
| | | | | | | 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
* The address is fetched while the key still turns in the lock: sign-in races ↵Adam Soltys2026-08-281-0/+15
| | | | | | | a registrar lookup against the seed work and seeds the name before first paint, so the Receive pane opens straight onto its QR instead of "setting up your payment address" Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_019eA8JRq9Wr7z2fRQk99HLi
* The wallet arrives without ceremony: sign-in swaps screens instantly (a ↵Adam Soltys2026-08-271-0/+3
| | | | | | | one-shot no-anim flag on the handover), and the spending-setup ask holds its tongue until the name restore has settled instead of flashing while the answer is seconds away 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/+23
| | | | | | | 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
* The app arrives in movements: real ESM chunks split the bundle (entry 333KB, ↵Adam Soltys2026-08-261-1/+1
| | | | | | | 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
* A wallet keeps one name per domain, not one name total: staging and mainnet ↵Adam Soltys2026-08-181-3/+37
| | | | | | | 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
* The claim field greets you by name: your nostr username arrives pre-filled ↵Adam Soltys2026-08-171-0/+24
| | | | | | | when the registrar confirms it's free (or already yours) — editable, never auto-claimed Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01JtaatFfAbx91Nh3xqKVqKz
* Typed names take the instant rail too: a Send button outranks the Zap for ↵Adam Soltys2026-08-171-4/+18
| | | | | | | our domains, resolving to the ark address instead of an LNURL round-trip that needs the recipient awake Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01JtaatFfAbx91Nh3xqKVqKz
* Our names get a second answer: when DNS is late the registrar speaks for its ↵Adam Soltys2026-08-171-9/+26
| | | | | | | own records, and unknown staging names say so instead of zapping a webroot that only serves HTML Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01JtaatFfAbx91Nh3xqKVqKz
* Staging names its own: mutinynet claims land at @staging.coinos.io — play ↵Adam Soltys2026-08-171-15/+19
| | | | | | | personas and their testnet addresses stop squatting the real namespace Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01JtaatFfAbx91Nh3xqKVqKz
* Mutinynet gets the full name experience: the payment-address pane, the claim ↵Adam Soltys2026-08-171-1/+6
| | | | | | | card, the re-registration — staging stops showing raw ark addresses to people who just claimed a name Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01JtaatFfAbx91Nh3xqKVqKz
* Username and display name go their separate ways: the handle wears a frozen ↵Adam Soltys2026-08-151-5/+8
| | | | | | | @coinos.io so it reads as address and NIP-05, renames claim before the profile publishes, and nip05 rides along with lud16 Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_014tE4U5nEjGTQy8PwYqamfv
* A new name offers its hand to the address: saving a renamed profile proposes ↵Adam Soltys2026-08-151-0/+4
| | | | | | | the matching @coinos.io, and any rename repoints the kind-0 lightning address so zaps never chase a released name Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_014tE4U5nEjGTQy8PwYqamfv
* The wizard waits out a signer login; a picked recipient shows its work, and ↵Adam Soltys2026-08-081-0/+9
| | | | | | search answers from memory before the network Co-Authored-By: Claude Fable 5 <[email protected]>
* More options also points at Savings for a plain on-chain addressAdam Soltys2026-08-041-2/+7
| | | | | | | | | Not a pane of its own: a regular Bitcoin address means receiving to Savings, so the entry switches the account selector over and lets the savings receive view show its fresh address. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_015QwAtBSpKvECqp1rHLHjJk
* One More-options link replaces the BOLT 12 link on ReceiveAdam Soltys2026-08-041-14/+122
| | | | | | | | | Behind it: a one-time BOLT 11 invoice (minted by the ASP), the reusable BOLT 12 offer flow, the wallet's raw ark address, and the CLINK offer code — each on its own pane so only one scannable code shows at a time. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_015QwAtBSpKvECqp1rHLHjJk
* The npub default never displaces a custom nameAdam Soltys2026-08-041-1/+9
| | | | | | | | | | | adoptIdentity claimed the identity npub whenever the local name differed — and claim() releases the previous name, so a nostr login could delete the user's custom address from the registrar (it did). It now asks the registrar first and yields to any custom name. The registrar's /pubkey lookup also matches manager keys now and prefers real names over npub defaults, so restored wallets find their name again. Co-Authored-By: Claude Fable 5 <[email protected]>
* Copy joins the pencil inside the address boxAdam Soltys2026-08-041-8/+13
| | | | Co-Authored-By: Claude Fable 5 <[email protected]>
* Choosing a name gets a page of its ownAdam Soltys2026-08-041-5/+17
| | | | | | | The pencil opens a dedicated screen — no balances, no tabs — wired into the history stack like the other sub-pages. Co-Authored-By: Claude Fable 5 <[email protected]>
* Pencil in a boxAdam Soltys2026-08-041-1/+1
| | | | Co-Authored-By: Claude Fable 5 <[email protected]>
* A proper pencilAdam Soltys2026-08-041-1/+1
| | | | Co-Authored-By: Claude Fable 5 <[email protected]>
* A quieter pen inside the address box; the header avatar stops flashingAdam Soltys2026-08-041-5/+5
| | | | | | | | The avatar img was recreated on every render — boot's render bursts made it flash; there is exactly one header avatar, so the node is reused until the profile actually changes. Co-Authored-By: Claude Fable 5 <[email protected]>
* The name is edited where the name is — a pencil by the addressAdam Soltys2026-08-041-7/+12
| | | | Co-Authored-By: Claude Fable 5 <[email protected]>
* Offer the custom-name form right on the Receive paneAdam Soltys2026-08-041-0/+6
| | | | | | | Anyone still on the npub-shaped default sees their address here, not in the collapsed corner of Settings where the claim form lived. Co-Authored-By: Claude Fable 5 <[email protected]>
* Claim with the login identity, so a migrated name can come homeAdam Soltys2026-08-041-1/+5
| | | | | | | | | | The registrar now lets a migrated coinos user's nostr identity (re)take its own name — migration could land the claim in whatever wallet ran it, stranding the name there. The claim form signs with the (re)attached login identity when there is one; the wallet key alone could never prove the takeover. Co-Authored-By: Claude Fable 5 <[email protected]>
* Drop the BOLT 12 explainer blurbAdam Soltys2026-08-041-1/+0
| | | | Co-Authored-By: Claude Fable 5 <[email protected]>
* Receive: the BOLT 12 section takes over the cardAdam Soltys2026-08-031-2/+4
| | | | | | | | | Opening it hides the payment address and its QR — two payment codes on one screen is a good way to have someone scan the wrong one. "Hide" becomes "Back", which is what it now does. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Onboarding: a step for migrating a legacy coinos.io accountAdam Soltys2026-08-031-0/+2
| | | | | | | | | | | | | | | | | | | Between "do you have a nostr account" and claiming a username, new users are asked whether they already have an account on the original coinos. Yes hands off to coinos.io/migrate with their fresh v3 payment address and a return URL — they're already signed in there, so no password or key ever travels. That page sweeps the balance over Lightning and renames the old account, releasing the username. Coming back, coinos.io returns ?migrated=<username>; v3 claims that name on the registrar (retrying for a minute, since the registrar's "is this a legacy user?" guard caches), and their lightning address now resolves to the v3 wallet. The legacy page ships in coinos-ui and is NOT deployed yet. 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-3/+76
| | | | | | | | | | | | | | | | | | 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
* First-run onboarding: a five-step wizard and a coach-mark tourAdam Soltys2026-08-021-0/+2
| | | | | | | | | | | | | | | | | | | Fresh devices land in a full-screen flow instead of the unlock screen: welcome → "do you have a nostr account?" (sign in brings wallet, name, and profile along; No mints a wallet on the spot) → claim a username (the background npub-default only surfaces a Keep button; a custom claim — or a restored login's recovered name — advances by itself) → pick a punk or upload a photo (nostr.build), published as the kind-0 name+picture → confetti. "Enter coinos" drops into a three-stop tour: the avatar menu, the Spending/Savings toggle, and the tabs with the swipe tip — each highlighted by a measured gold ring over a dimmed page. One time only (localStorage flag), with an "I already have a wallet" escape to the classic screen. New seams: names.namesClaimForm, messages.publishProfile. 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-021-2/+2
| | | | | | | | | 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
* Profiles: view pages, editable own kind 0, zap recipients in historyAdam Soltys2026-08-021-0/+2
| | | | | | | | | | | | | | | | | | | 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
* 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-011-0/+3
| | | | | | | | | | | | | | 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]>
* 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-011-25/+28
| | | | | | | | | | | | | | | 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-011-6/+21
| | | | | | | | | | | | 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-011-43/+41
| | | | | | | | | | | | | | | | | | | | | | | | | | 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]>
* 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]>
* Coinos v3: rebrand from Hal, payment addresses move to @coinos.ioAdam Soltys2026-08-011-8/+8
| | | | | | | | | | | | | | | 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-1/+9
| | | | | | | | | | | | | | | | | | | | | | | | | 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/+179
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]>