aboutsummaryrefslogtreecommitdiffstats
path: root/src/features/ark.js
Commit message (Collapse)AuthorAgeFilesLines
* The board drive gets a second witness: the wallet's own chain view vouches ↵Adam Soltys2026-08-181-0/+7
| | | | | | | for confirmations when the ark esplora is rate-limited or walled off, and chain errors finally surface instead of masquerading as unconfirmed Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01JtaatFfAbx91Nh3xqKVqKz
* A confirmed board stops waiting on a flaky tip: confirmation itself counts ↵Adam Soltys2026-08-181-1/+4
| | | | | | | for one, and a failed mailbox read no longer starves the drives that only need the chain Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01JtaatFfAbx91Nh3xqKVqKz
* The top-up prompt counts money already on its way: a board awaiting ↵Adam Soltys2026-08-181-0/+7
| | | | | | | confirmation quiets both the card button and the wizard step Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01JtaatFfAbx91Nh3xqKVqKz
* The ark send review joins the boarding party: an amount Spending can't cover ↵Adam Soltys2026-08-181-1/+11
| | | | | | | swaps Send for the board door instead of walking into the insufficient-balance wall Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01JtaatFfAbx91Nh3xqKVqKz
* The on-chain send from Spending shows both doors: the cooperative exit it ↵Adam Soltys2026-08-181-1/+13
| | | | | | | always was, with the unilateral hatch one quiet link away — promoted to a button the moment the server stops cooperating Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01JtaatFfAbx91Nh3xqKVqKz
* Money finds its way to Spending: the setup wizard ends with a top-up step ↵Adam Soltys2026-08-181-0/+6
| | | | | | | when Savings can fund one, and a low Spending face wears a one-tap Top up from Savings button Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01JtaatFfAbx91Nh3xqKVqKz
* An empty Spending stops being a dead end: the zap button becomes the door to ↵Adam Soltys2026-08-171-0/+18
| | | | | | | boarding, prefilled with what the payment needs and open to more Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01JtaatFfAbx91Nh3xqKVqKz
* The balance card's switch becomes Manage accounts, the header chip retires, ↵Adam Soltys2026-08-151-9/+39
| | | | | | | Move money is gone — and pasting an on-chain address in a Spending wallet just exits ark to it Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_014tE4U5nEjGTQy8PwYqamfv
* The social layer grows up: a header magnifier finds anyone on nostr, ⚡ ↵Adam Soltys2026-08-151-23/+46
| | | | | | | zaps your saved amount in one tap (asking once if you haven't picked one), and tapping a note opens its thread Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_014tE4U5nEjGTQy8PwYqamfv
* Zaps too gather what they need: several coins each lock an HTLC under one ↵Adam Soltys2026-08-141-3/+13
| | | | | | | payment hash and the server sums them — the fragmented-balance error is gone from Lightning as well Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_014tE4U5nEjGTQy8PwYqamfv
* A mailbox backlog claimed at sign-in reads as the old payments they are, not ↵Adam Soltys2026-08-141-20/+57
| | | | | | | a wall of seconds-ago windfalls; a gift send knows itself in history — status shown, link recopyable, revoke at hand Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_014tE4U5nEjGTQy8PwYqamfv
* Settings grows a Notifications page — per-category switches the notifier ↵Adam Soltys2026-08-141-2/+4
| | | | | | | honours server-side — and the status bar gets a real coinos badge instead of a black blob Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_014tE4U5nEjGTQy8PwYqamfv
* Pay what the route costs: sends quote the network fee up front (zero between ↵Adam Soltys2026-08-141-14/+47
| | | | | | | coinos wallets), lock exactly that, and a refunded miss retries with one more sat Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_014tE4U5nEjGTQy8PwYqamfv
* A waiting board says what it waits for: the Moving line counts ↵Adam Soltys2026-08-091-5/+17
| | | | | | | confirmations, a snag shows itself, and one stuck action no longer starves the queue behind it Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01DbA53PJA8fgSvMcEzfuw22
* Profiles wear their latest notes, each one zappable; a zap with no ark ↵Adam Soltys2026-08-081-38/+125
| | | | | | address to land on becomes a gift only their key can open Co-Authored-By: Claude Fable 5 <[email protected]>
* Spending trusts its own ledger: gifts read persisted ark state, and Savings ↵Adam Soltys2026-08-051-2/+7
| | | | | | | keeps its pending line to itself Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01M5kFodm9azZJVTQxnkDdKe
* A wallet too small to renew warns before its coins expire; dust never diesAdam Soltys2026-08-041-1/+44
| | | | | | | | | | | | | | | | | | The warning: when the Spending balance minus round fee is under the 330-sat round minimum AND a vtxo enters the renewal window, auto-refresh can't save the coins — a wallet-screen notice (and a daily local notification, when permitted) says to receive any amount or spend before the deadline. New walletNotices feature hook renders under the balance card. The amnesty: sub-dust vtxos have no unilateral exit to protect, and regtest proves captaind keeps cosigning their spends past expiryHeight — even after the watchman claims the backing on-chain (tools/ark/expired-dust-test.js). Only hal's own receive validation stood in the way; it now exempts sub-330 vtxos from the expiry check. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_015QwAtBSpKvECqp1rHLHjJk
* Auto-withdraw runs with the app closed; fix the dead Back on the legacy stepAdam Soltys2026-08-031-0/+45
| | | | | | | | | | | | | | | | | | | | | The service worker can't watch a relay, so it doesn't try: the app resolves the destination to something concrete (ark address, LNURL endpoint, or on-chain script) and mirrors it beside the keys the background NWC payer already uses. On a push wake-up — which is exactly when money has just landed — the worker re-checks the same gate and sends, writing the outcome back into the mirror for the app to absorb on next open. A partial on-chain forward still waits for the app, since carving an exact vtxo needs more round trips than a worker is promised. writeBgMirror now carries forward the NWC connections and offer when another caller refreshes the mirror, instead of blanking them. And onboarding's legacy step loses its Back: the wallet exists by then, so returning to the nostr question just bounced straight forward again and the link looked broken. Its copy no longer promises a rename either. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Move money: shorter amount placeholder, row wraps on narrow phonesAdam Soltys2026-08-031-1/+1
| | | | | | | | | | The placeholder repeated a minimum the line below already states, and it was clipped anyway — the amount field had 14px of usable width at 360px because "Move to Spending" crushed it. The row wraps to two full-width lines when it's tight and stays on one line when there's room. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Auto-withdraw: forward Spending onward past a thresholdAdam Soltys2026-08-031-1/+182
| | | | | | | | | | | | | | | | | | | | Settings → Payments takes a destination, a threshold, and an optional amount to keep behind. When Spending crosses the threshold the wallet sends the rest on by itself, resolving the destination the same way the send form does — an ark address goes native and free, a payment name prefers its ark instruction before falling back to lightning, an npub tries its ark advert then its lightning address, and an on-chain address becomes a cooperative offboard (carving out an exact-amount vtxo first when it isn't sending everything, since offboards move whole vtxos). Money moving without a human watching deserves guardrails: one transfer at a time, never while another ark action is in flight, a fifteen-minute backoff after a failure with the reason kept for the card, destinations validated before it can be switched on, and dust amounts skipped. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Zap: resolve inside a skeleton of the card that's comingAdam Soltys2026-08-031-4/+19
| | | | | | | | | | | | | | The inline-progress attempt still read as a flash — the send form was visible behind it with the raw npub, its verification block and the gift button, then all of that was replaced. Now both resolution phases (ark advert, then lightning) render the SHAPE of the destination card: same title, same recipient chip, a spinner where the amount form will be, and a working Back. The card fills in instead of the screen changing twice, and the two flows share one title so it never re-titles at the handoff. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Pay screens: bigger recipient chip, no loading-screen flashesAdam Soltys2026-08-031-1/+4
| | | | | | | | | | | | Resolving a recipient (ark advert → BIP-353 → lightning) no longer takes over the screen twice on the way: both steps keep the send form in place and report progress as one inline line, so picking someone goes straight to something actionable. The recipient chip on the zap card gets a large variant — a 30px avatar and full-size name, since it's who the money is going to. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Unilateral exit button: drop the coin countAdam Soltys2026-08-031-1/+1
| | | | | | | | | "Move to Savings without the server" — the vtxo count was internal detail in a button most users will never press. It still gates whether the button appears at all. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Move money: Max on both directions, inline in the amount fieldAdam Soltys2026-08-031-7/+34
| | | | | | | | | | | | Into Spending gains a Max (fee-aware — it asks the tx builder what a sweep would actually deliver, so the mining fee can't make the board fail), and both directions now carry Max as a small affordance inside the input's right edge instead of a button eating a third of the row. It only appears when there's a balance to max. Balance face height trimmed to match its shorter content. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
* Zap screens show the recipient, cap amounts to Spending, faster searchAdam Soltys2026-08-031-3/+6
| | | | | | | | | | | | | | | | | | | The zap flows (ark-direct and Lightning) show the recipient's avatar chip instead of a bare name, and their LNURL ceiling is now capped by our own Spending balance — when Spending can't cover their minimum the amount field is replaced by a plain "not enough in Spending" notice rather than an impossible "min 1 · max 0" range. The confirm path enforces the same cap. Recipient search feels instant now: the first short query fires after 120ms (the wait users actually notice) and longer mid-word queries wait 500ms to skip characters still being typed; cached prefixes stay instant. Onboarding success screen: check below the title and larger. 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
* 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-021-1/+20
| | | | | | | | | | | | | | | | | | | 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
* 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-021-1/+1
| | | | | | | | | | | | | | | 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-021-29/+3
| | | | | | | | | | | | | | | | | | 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
* 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]>
* 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]>
* 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]>
* Zappable offer codes and per-name Lightning offersAdam Soltys2026-08-011-3/+4
| | | | | | | | | | | | | | | | | | | | | | | | | 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/+5
| | | | | | | | | | | | | | | | | | | | Receiving: a Settings card claims a username, signed with the wallet's nostr key; the new names registrar (names/server.js, deployed at names.halwallet.app) enforces first-come ownership by that key and mirrors the claim into a DNSSEC-signed TXT record at <name>.user._bitcoin-payment.halwallet.app whose BIP-21 URI carries the wallet's reusable ark address. The record re-registers itself if the ark address ever changes, and a collapsible hint shows the single CNAME a user adds to serve the same name from their own domain (BIP-353 supports delegation when both zones are DNSSEC-signed). Sending: a pasted name@domain resolves over DNS first — two independent DoH resolvers must agree and both report the DNSSEC AD flag (full client-side chain validation is the stated follow-up) — an ark= instruction pays natively, an on-chain path fills the send form, and a name with no DNS record falls back to the Lightning-address zap flow. Co-Authored-By: Claude Fable 5 <[email protected]>
* Simplify: one Lightning path, one balance, fewer featuresAdam Soltys2026-08-011-282/+80
| | | | | | | | | | | | | | | | | | | | | | | | | Dropped: silent payments (feature, wallet chains, scan worker, indexer config), Boltz submarine/reverse swaps (feature + swap.js + tools), the third-party ark swap bridge (config, client flows, settings card — the ASP's own Lightning send is the one path now), and the Consolidate coins settings action. The NWC pouch is gone with them: background answering now spends the WHOLE ark balance. The IndexedDB record mirrors the real manager state plus the chain-3 keys for its live coins and a change-index window; the worker pays through the ASP exactly like the open app, and the app folds the worker's spends, change, and key allocations back in on connect (additive merge + reconcile against the server). A leftover chain-6 pouch from the short-lived pouch era is swept home automatically using the keys stored in its own record. The stated tradeoff: same-origin script access now reaches the ark balance, not a carve-out — the ark balance is walking-around money, the on-chain wallet's keys are never mirrored, and per-connection NWC budgets remain the enforced ceiling. Bundle: 726KB -> 602KB. Co-Authored-By: Claude Fable 5 <[email protected]>
* Background NWC: the worker now answers — and pays — from the pouchAdam Soltys2026-08-011-2/+110
| | | | | | | | | | | | | | | | | | | | | | | 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]>
* Split device sync into per-domain slots with bounded, dirty-only publishesAdam Soltys2026-08-011-3/+10
| | | | | | | | | | | | | | | | 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]>
* Reconcile vtxos against the server before a headless NWC paymentAdam Soltys2026-08-011-0/+5
| | | | | | | | | | The sync merge is additive by design, so a vtxo spent on one device can survive in another device's state looking spendable. The interactive send path reconciles on intent; the NWC path connected and spent blind, so a wallet answering from stale state picked an already-spent coin and the ASP refused with 'vtxo … is not spendable (state: spent)'. Co-Authored-By: Claude Fable 5 <[email protected]>
* Wake hal for wallet-connect requests while it is closedAdam Soltys2026-08-011-0/+6
| | | | | | | | | | | | | | | | | | | Step 3, first half. Android Chrome freezes backgrounded PWAs and closes their relay sockets, so a phone with hal "open" answers nothing — which is what made Amethyst spin. Push is the only way to reach a frozen app. - service worker gains a `push` handler. It deliberately does NOT spend: if a window is open it nudges it to re-listen, otherwise it raises a notification that opens hal on tap. Putting a spend path in the worker is the remaining half and wants its own testing. - hal-side: request permission, subscribe with the notifier's VAPID key, and register its service pubkeys with nwcpush.coinos.io. Turning it off deregisters, unsubscribes, and clears the pouch — no reason to leave spendable keys where a worker can read them if nothing will. - a Settings toggle, and the ark feature exposes the ASP context the pouch record needs. Co-Authored-By: Claude Fable 5 <[email protected]>
* Add Nostr Wallet Connect (NIP-47) — hal as a wallet serviceAdam Soltys2026-08-011-0/+47
| | | | | | | | | | | | | | | | | | | | | | | | | Other apps can connect over nostr and pay from this wallet without touching its keys, within a per-payment and daily budget the user sets, revocable at any time. hal holds no native Lightning balance, so every operation runs over Ark: pay_invoice takes the swap bridge when configured and falls back to the ASP's lightning-send, make_invoice mints on the ASP. NWC availability therefore tracks Ark availability. - kinds 13194/23194/23195, nip44_v2 and nip04, replying in whichever scheme the request used; every request gets a reply so clients never hang - each connection gets its own service keypair, so revoking one can't affect the others and none is tied to the user's nostr identity - budgets are checked BEFORE spending, not reconciled after - requests are ignored unless signed by that connection's client key, and replayed/stale events (>120s) can't re-trigger a payment - Settings card to create, show (URI + QR) and revoke connections, and it says plainly that this only works while a tab is open tools/nwc-test.js drives it as a real client: both encryption schemes, budget refusal with a genuine over-limit invoice, unsupported-method errors, and a request from an unauthorized key being ignored. Co-Authored-By: Claude Fable 5 <[email protected]>
* Stop the nostr sync snapshot from importing another ASP's coinsAdam Soltys2026-08-011-2/+15
| | | | | | | | | | | | | | | | | | | | | The phantom balance came back through the sync path. Snapshots published by older builds carry no ASP tag, so the tag check passed them, and with the local namespace empty the merge returned the snapshot wholesale — writing second.tech's vtxos straight into the coinos namespace, where the manager then stamped them as coinos'. Two fixes, both grounded in the fact that a vtxo names its own cosigner: - the sync path derives the snapshot's true owner from its vtxos and parks it under that owner instead of merging when it isn't provably ours - ArkManager.init drops any state whose coins were cosigned by a different server, whatever the stamp claims. Stamps are something we wrote; the vtxo bytes are evidence. Found by tracing localStorage reads in the live tab: the namespaced key was being *created* during load, which pointed at the merge rather than at storage keying. Co-Authored-By: Claude Fable 5 <[email protected]>
* Repair Ark state that an earlier build stamped with the wrong serverAdam Soltys2026-08-011-1/+36
| | | | | | | | | | | | | | | The first namespacing attempt could brand one ASP's coins with whichever server happened to be connected, filing them under the wrong namespace — a phantom balance that survives both later fixes because the damage is already written to localStorage. loadArkState now checks the stamp against the vtxos themselves. A vtxo names its cosigner in its own bytes, which is fact; the stamp is just something we wrote. When they disagree the state is misfiled: it moves to a holding key under its true owner and this namespace shows nothing. adoptArkState claims from that holding area when the matching server connects. Co-Authored-By: Claude Fable 5 <[email protected]>
* Never render another ASP's Ark state before connectingAdam Soltys2026-08-011-7/+28
| | | | | | | | | | | | | | | | The previous fix still let a phantom balance through: loadArkState fell back to the pre-namespacing key when a namespace was empty, and the UI renders from that before any server is contacted — so the guard, which runs on connect, never got a say. loadArkState now returns only the namespaced state. Legacy state is claimed through adoptArkState(serverPubkey), which proves ownership first: from the state's own stamp, or failing that by decoding a vtxo, whose wire format names its cosigner. No network call, and it cannot guess wrong. On a match the state moves into the namespace and the legacy key is retired; on a mismatch it is left untouched for its real owner. Co-Authored-By: Claude Fable 5 <[email protected]>
* Keep Ark state per ASP, not just per walletAdam Soltys2026-08-011-15/+47
| | | | | | | | | | | | | | | | | | | | | Switching providers showed the previous server's balance and tried to resume its in-flight board ("1 deposit confirming" against a server that had never heard of it). A vtxo is only meaningful against the ASP that cosigned it, so state must be scoped to both the wallet and the server. - ark state keys gain a namespace derived from the ASP endpoint, so switching providers switches state and switching back restores it - state is stamped with the server pubkey; on connect a mismatch is discarded rather than displayed. This also self-corrects the one-time adoption of pre-namespacing state, which can only guess which ASP it belonged to - mergeArkStates refuses to fuse two servers' states, and the nostr sync snapshot carries (and filters on) the ASP it came from, so a device on a different provider can't inject foreign vtxos Nothing is deleted: each ASP's state lives under its own key, and the legacy key is left in place for whichever server rightfully claims it. Co-Authored-By: Claude Fable 5 <[email protected]>