aboutsummaryrefslogtreecommitdiffstats
path: root/src/features/ark.js
Commit message (Collapse)AuthorAgeFilesLines
...
* 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]>
* Settings: coinos ASP presets + swap-bridge providerAdam Soltys2026-07-311-3/+178
| | | | | | | | | | | | | | | - ark presets gain coinos entries (ark.coinos.io on mainnet, ark-staging.coinos.io on mutinynet) alongside Second's - new per-network swap-bridge setting (preset/custom URL + access token) with its own Settings card When a bridge is configured, a bolt11 quote asks it first and shows its fee; paying locks an HTLC only the bridge can claim, and only by revealing the invoice preimage. Any failure — unreachable bridge, refused swap, unpayable invoice — refunds and falls back to the ASP's own lightning-send, so the payment never dead-ends. Hanging swaps are refunded on reconnect. Co-Authored-By: Claude Fable 5 <[email protected]>
* Fix send-form UX around lightning pays; harden ln state machinesAdam Soltys2026-07-311-29/+49
| | | | | | | | | | | | | | | | | | | - a rejected cosign (gRPC INVALID_ARGUMENT) returns the input vtxo to spendable and fails the action instead of leaving it pending forever — the stuck-pending state that pushed small zaps onto Boltz's 25k minimum - ark ln-pay now takes over when funds are settling (retrying quote) rather than falling through to Boltz - typing a destination clears stale send errors (also cleared on tab switch); paste path likewise - typed lightning addresses no longer auto-advance mid-keystroke (the matcher fired at `[email protected]` before `.com`, yanking the form away): the send form offers an explicit "Zap <addr>" button; pasted/scanned addresses still advance immediately - ln-recv: HTLC vtxos anchored to a not-yet-confirmed pool round park and retry instead of throwing; the requested HTLC expiry is anchored on first prepare so retries can't inflate it past the server's grant Co-Authored-By: Claude Fable 5 <[email protected]>
* Dust-isolate arkoor outputs; prove lightning E2E on regtestAdam Soltys2026-07-311-2/+2
| | | | | | | | | | | | | | | | | The server rejects an arkoor mixing dust and non-dust outputs, which broke small Lightning payments (a 45-sat HTLC next to change) and tiny plain sends. Mirror bark's isolation: dust outputs route through a combined isolation output spent by a fanout tx, splitting a non-dust output to pad the dust below-threshold. Outputs can therefore split across two vtxos, so the manager now classifies results by policy instead of index (legacy single-field actions still resume). Regtest now runs the full stack: dedicated cln-ark node (secondark/cln-hold, hold plugin + xpay) wired into captaind with a channel to the coinos dev clc node. tools/ark/ln-e2e-test.js proves pay (5000 sat and a 25-sat dust zap), hold-invoice receive with claim, and spending the claimed vtxos. Co-Authored-By: Claude Fable 5 <[email protected]>
* Add Ark <-> Lightning via the ASP (captaind-native submarine swaps)Adam Soltys2026-07-311-7/+231
| | | | | | | | | | | | | | | | | | | | | | | | | | | The ASP is the HTLC counterparty (second.tech's protocol), so no external swap provider is needed: paying a bolt11 converts pubkey vtxos into ServerHtlcSend vtxos the server can only claim by revealing the invoice preimage; receiving grants ServerHtlcRecv vtxos claimed by revealing ours. Failure paths cosign the HTLC back (revocation) — proven end-to-end against regtest captaind, which independently derives the HTLC taproots. - ark/lightning.js: bolt11 decode, lightning fee schedules, all pay/receive gRPC-web wrappers (cosign, initiate, status, revocation, hold-invoice receive flow, anti-DoS attestation) - ark/send.js: arkoor builder generalized to policy outputs (pubkey / serverHtlcSend / serverHtlcRecv), two-leaf taproot trees, HTLC clause scripts, reusable per-part cosign helpers for package RPCs - ark/manager.js: ln-pay and ln-recv crash-safe action state machines (deterministic receive preimages from the seed, chain 5), mailbox lightning notifications, unpayable-invoice -> instant revocation - proto.js: lightning ArkInfo fields + mailbox lightning message decode - validate/refresh: policy-aware vtxo validation and reconstruction - features: bolt11 routes via ark when the balance covers it (Boltz stays the fallback), lightning-invoice receive on the Ark pane - tools/ark/ln-test.js: harness proving HTLC cosign + revocation against captaind; manager-test.js updated for the consolidated bc-container chain Co-Authored-By: Claude Fable 5 <[email protected]>
* Add NIP-57 Lightning zaps (npub / lightning address / lnurl)Adam Soltys2026-07-301-0/+5
| | | | | | | | | | | | | | | | | | | | | | | | | | Send a Lightning zap to a nostr npub, a lightning address (name@domain), or an lnurl. New src/lnurl.js does the resolution half — target → LNURL-pay params → signed kind-9734 zap request → callback → bolt11 — and the new zaps feature (src/features/zaps.js) drives the amount/comment UI, then hands the invoice to the existing Boltz submarine-swap flow via a new swaps.startLnPay hook, so there's one tested pay/review/confirm/success path. Routing: a bolt11 is still caught by swaps; an npub still prefers an instant free Ark zap when Ark can serve it, and only falls through to a Lightning zap otherwise — ark's "no Ark address" screen also offers an explicit "Zap over Lightning" fallback (gated on the zaps feature's canLnZap). A real zap receipt is attributed only when the server advertises allowsNostr and we know the recipient's pubkey; otherwise it degrades to a plain LNURL-pay. Nostr seam gains nostrSign (finalize without publishing), nostrProfile (kind-0 with lud16/lud06), and nostrRelays. Lightning addresses are debounced in matchSendText since their regex matches mid-typing. Verified: unit tests for parse/encode/zap-request; a live resolve→signed 9734→payable mainnet bolt11 against coinos; and an in-browser DOM smoke (zap form + LNURL params over CORS + allowsNostr hint + handoff). Co-Authored-By: Claude Opus 4.8 <[email protected]>
* Slim ark state for sync so it fits the relay's 64KB event capAdam Soltys2026-07-061-6/+28
| | | | | | | | | | | | | | Cross-device ark sync silently failed: the full snapshot (deep-genesis vtxo bytes + done-action signing hex) was ~153KB and relay.coinos.io rejects events over 64KB, so no publish ever landed and other devices never saw the balance. The synced ark state is now slimmed — spent vtxos drop their signed bytes (kept only for spendable/pending, which a device might spend/exit), done board/offboard/exit actions keep just the fields history labels need (hex stripped), in-flight actions and movements beyond the last 300 are dropped. Local storage keeps the full state; only the published copy is slim. ~150KB -> well under 64KB. Co-Authored-By: Claude Fable 5 <[email protected]>
* tmp: instrument ark connect + sync publish (debug)Adam Soltys2026-07-061-1/+2
|
* Publish ark state on connect so idle devices share their balanceAdam Soltys2026-07-061-0/+6
| | | | | | | | | | | | | Cross-device ark sync only re-published on a state change (send/board), so a device holding ark funds but making no transaction never wrote its state to the relay — another device could never see those coins (and a legacy shared-tag snapshot that got clobbered to zero-ark stayed that way). connectArk now calls saveCache once on connect when there are vtxos, publishing this device's balance to its per-device slot so every device's ark funds reach the relay and get merged, no transaction needed. Co-Authored-By: Claude Fable 5 <[email protected]>
* Show ark balance for watch-only wallets (read from persisted state)Adam Soltys2026-07-061-4/+11
| | | | | | | | | | | | | | After a refresh a wallet reverts to watch-only unless the seed was saved to device — and ark balance/history were gated behind arkAvailable() (which excludes watch-only), so the on-chain balance showed but the ark balance vanished. The ark state is persisted locally and should display read-only exactly like the on-chain balance. arkStateNow() now reads it whenever Ark is configured for the network, regardless of watch-only; the Exit link (which needs keys to act) is hidden for watch-only. Verified: full wallet shows balance + Exit link, a locked/watch-only wallet shows the balance alone. Co-Authored-By: Claude Fable 5 <[email protected]>
* Consolidate ark exit into one page: cooperative + unilateralAdam Soltys2026-07-061-41/+80
| | | | | | | | | | | | | The cooperative offboard was buried under the receive-tab board form and the unilateral exit lived in Settings — both unintuitive. Now a small 'Exit' link next to the Ark balance opens a dedicated page that explains and offers both: cooperative (server co-signs, fast, no on-chain funds needed, small server fee) and unilateral (trustless, timelocked, pays mining fees). In-flight exit progress (hop counter, timelock countdown, cancel) moved here from Settings; the receive pane loses the offboard button and Settings loses the exit button. Co-Authored-By: Claude Fable 5 <[email protected]>
* No ark jitter on refresh: render from persisted state before connectAdam Soltys2026-07-061-11/+31
| | | | | | | | | | | | | | | The balance line and history-row icons read the LIVE ark manager, which is null until connectArk() finishes its async handshake — so on refresh the ark balance briefly vanished and ark rows flashed the Bitcoin icon before flipping to the ark mark. Read-only hooks (balance, decorateTxRow, historyEntries, detail views) now fall back to the persisted ark state from storage via arkStateNow(), so they paint correct on first render; the manager connecting later just keeps it fresh with identical data (no visible change). Verified: balance present and board row shows the ark icon from the first 50ms sample after reload. Co-Authored-By: Claude Fable 5 <[email protected]>
* History: Ark glyph in the row circle (no text chip); real Bitcoin logoAdam Soltys2026-07-061-3/+4
| | | | | | | | | | | Off-chain Ark rows (arkHistoryItem) showed an arrow + a redundant 'Ark' text chip; now the Ark mark sits in the direction-colored circle and the chip is gone — the label + signed amount carry direction. The detail header gets the badge instead of the chip. And the receive selector's Bitcoin option uses coinos's real bitcoin.svg (orange disc + tilted white B) instead of a bare ₿ character. Co-Authored-By: Claude Fable 5 <[email protected]>
* Icons in the receive selector and on silent-payment history rowsAdam Soltys2026-07-061-1/+1
| | | | | | | | | | | | | | The receive address-type picker was a native <select>, which can't render SVG in <option>s. Replace it with a lightweight custom dropdown (icon-select) that shows a glyph per rail — ₿ Bitcoin, ⚡ Lightning, the Second mark for Ark, 🕶️ Silent payment — closing on select or an outside tap. Receive modes gained an icon field; the address option is built in app.js. Silent-payment receipts (which merge into history as on-chain-looking rows) now get a rail glyph + SP tag via a decorateTxRow so they're distinguishable, matching how ark/gift rows already decorate. Co-Authored-By: Claude Fable 5 <[email protected]>