Commit Graph
15227 Commits
Author SHA1 Message Date
Vitor PamplonaandGitHub 24e9cf2aaa Merge pull request #3263 from vitorpamplona/claude/gracious-archimedes-wfvcri
Add macOS code signing + notarization for DMG and amy CLI
2026-06-18 09:14:25 -04:00
Claude 87c16afe1d refactor: move amy.rb into cli/packaging/homebrew (was root packaging/)
The Homebrew formula is the cli module's product (amy), and the CLI already
owns its packaging artifacts under cli/packaging/ (cli/packaging/macos/
amy.entitlements). The root packaging/ dir was new in this branch and held
nothing else, so co-locate the formula with the module that owns it and drop
the stray root dir. Updates the two BUILDING.md references.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015sso31DfSF9B6EFCVkEqWD
2026-06-18 13:13:43 +00:00
davotoula c9e8c84524 fix(l10n): list base sw in locales_config to match Swahili consolidation
The Swahili translation now lives at the base values-sw resource (the
sw-KE/sw-TZ dirs were consolidated away)
2026-06-18 11:44:00 +02:00
David KasparandGitHub 468966f57b Merge pull request #3262 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-06-18 11:42:15 +02:00
David KasparandGitHub 1dfa24390d Merge branch 'main' into l10n_crowdin_translations 2026-06-18 11:42:06 +02:00
Crowdin Bot 97e1a9bebf New Crowdin translations by GitHub Action 2026-06-18 09:40:57 +00:00
David KasparandGitHub b73890ac96 Merge pull request #3261 from nrobi144/feat/desktop-launch-optimization
feat(desktop): launch optimization foundation + icon-decode/relay-bootstrap fixes
2026-06-18 11:38:59 +02:00
nrobi144 96752bd21b docs(desktop): mark all in-scope launch-opt phases complete in the plan
Phase 1.4 (App() smoke test), Phase 2.4 (fixture-relay wire-up), and
Phase 5.2 (bootstrap-gate fix + regression tests) all land in commit
48a8178c9. The progress-log table, acceptance-criteria checkboxes, and
pending-work section are updated to reflect the new state. 278/278
desktopApp tests pass.

Only follow-up enhancements remain — the cold-fork shell driver, a
Compose-driving benchmark variant, and Phase 5.3 (sequential remember
chain in MainContent). None are required for the in-scope set.
2026-06-18 12:10:58 +03:00
nrobi144 48a8178c98 feat(desktop): App() Compose UI smoke tests + Phase 5.2 regression tests
Adds a `LaunchTestOverrides` bundle (default null in production) so
`App()` can be driven from `createComposeRule()` against the in-process
fixture relay instead of the OkHttp + kmp-tor + DesktopHttpClient stack
it normally constructs via `remember { … }`. `DesktopRelayConnectionManager`
gains a secondary constructor taking a `WebsocketBuilder` so the
`LocalRelayManager` composition local (typed as
`DesktopRelayConnectionManager?` and consumed by ~20 screens) does not
have to be relaxed.

`AppStateMachineTest` exercises four scenarios:

1. `appShowsLoginScreenWhenNoSavedAccountExists` — App() with no
   `accounts.json.enc` reaches LoggedOut and renders LoginScreen.
2. `appWithViewOnlyAccountReachesLoggedInWithoutCrashing` — App() with
   a pre-seeded ViewOnly account reaches LoggedIn end-to-end through
   `MainContent`, the deck columns, NWC wiring, etc.
3. `bootstrapSubscriptionFiresEagerlyEvenWhenRelayNeverConnects` —
   wires a `NeverConnectsWebsocketBuilder` so no connection ever opens,
   yet App() still reaches LoggedIn within 5s instead of the previous
   30s gate timeout. Direct regression test for the Phase 5.2
   bootstrap-gate removal.
4. `bootstrapSubscriptionFiresAtMostOncePerAccountLoad` — wraps the
   fixture builder with a `RecordingWebsocketBuilder` and asserts the
   bootstrap REQ does not loop or double-fire.

The `LaunchScenario` benchmark drops its private
`BenchmarkRelayConnectionManager` subclass in favor of the new
secondary `DesktopRelayConnectionManager(WebsocketBuilder)` constructor.

278/278 desktopApp tests pass.
2026-06-18 12:09:41 +03:00
David KasparandGitHub 24655a8a89 Merge pull request #3259 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-06-18 11:01:28 +02:00
Crowdin Bot a81b2b9994 New Crowdin translations by GitHub Action 2026-06-18 08:41:19 +00:00
David KasparandGitHub 4d8e3063c4 Merge pull request #3258 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-06-18 10:39:27 +02:00
nrobi144 d10c875d88 docs(desktop): refresh launch-opt plan pending list with App() blocker
Phase 1.4 / 2.4 / the four Phase 5.2 regression tests are all blocked on
the same broader App() dependency-injection refactor — relayManager,
localCache, localRelayStore, and subscriptionsCoordinator are still
constructed inside App() via remember { … }. The torManager slot has
been loosened to ITorManager in preparation, but the rest is wider work
than this session can absorb.
2026-06-18 11:14:28 +03:00
nrobi144 d2d044a634 refactor(desktop): relax App() torManager param to ITorManager interface
App() only consumes torManager.status, which is on ITorManager.
Loosening the parameter type lets future tests substitute a fake without
having to construct the concrete DesktopTorManager (which eagerly builds
a kmp-tor TorRuntime on first status access). No production behavior
change: DesktopTorManager already implements ITorManager and the existing
call site at Main.kt:633 upcasts naturally.

This is a small intermediate step on the road to the still-pending Phase
1.4 App() Compose smoke test, which is the last item blocked on broader
App() dependency injection (relayManager / localCache / localRelayStore
are still remember'd internally).
2026-06-18 11:13:54 +03:00
nrobi144 b14ee5ec5c feat(desktop): in-process relay seam, launch benchmark, bootstrap-gate removal
Phases 2.1/2.2/2.3, 3.1/3.2, 4, 5.2, and 6 of the launch-optimization plan
land together because they share a single set of seams and a single
benchmark report.

* InProcessWebsocketBuilder + LaunchFixtureRelay wrap quartz's existing
  InProcessWebSocket + NostrServer (with EmptyPolicy) so any test can
  drive a NostrClient against an in-memory relay seeded with arbitrary
  events. Roundtrip verified by LaunchFixtureRelayTest.

* LaunchFixture builds a deterministic 50-note synthetic home-feed
  snapshot from a fixed RNG seed (kind:1 + author kind:0 + kind:3 +
  kind:10002). A real-world JSONL artifact is a drop-in replacement.

* NoteCard gets a stable testTag + a CompositionLocal-backed
  onPlaced hook. Production overhead is one composition-local read
  plus one null check per placement (default
  LocalNoteCardInstrumentation = null).

* LaunchMarkers records named markers against TimeSource.Monotonic.
  LaunchScenario.coldBoot drives the AccountManager (ViewOnly path)
  + DesktopLocalCache + RelayConnectionManager + LocalRelayStore
  stack against the fixture relay and reports t_account_logged_in,
  t_first_event, t_n_events.

* LaunchBenchmark runs 2 warmup + 5 measured iterations, computes
  min/q1/median/q3/max, atomically writes the report file, and is
  skipped by default — opt in via AMETHYST_BENCH=true. Baseline +
  post-fix snapshots committed under desktopApp/benchmarks/.

* SubscribeBeforeConnectTest proves NostrClient / RelayPool queue REQs
  issued before connect() and flush them when the connection comes up.
  The bootstrap-config subscription in Main.kt drops its
  `connectedRelays.first { isNotEmpty() }` + 30s withTimeoutOrNull gate
  on the strength of that invariant — the subscription now fires
  eagerly and recovers when no relay ever connects instead of silently
  giving up after 30s.

All 274 desktopApp tests pass. No flaky tests introduced.
2026-06-18 11:10:14 +03:00
Claude cb5498ae4c perf(cli): drop Compose UI render stack from the amy runtime image
amy is headless and compiles against zero Compose UI (the Compose deps are
`implementation` in :commons, so they never hit the CLI compile classpath),
but they still rode the runtime classpath into the shipped image — ~29 MB of
Compose desktop render stack, including skiko's native .dylibs that enlarged
the macOS notarization surface.

Exclude skiko + the org.jetbrains.compose UI groups (ui/foundation/material/
material3/animation) from :cli runtimeClasspath. Keep androidx.compose.runtime
(snapshot state + @Stable/@Immutable) — that IS CLI-safe and used by commons
models/state. This avoids the commons → commons/commons-ui module split: the
single-module, feature-cohesive design (commons/ARCHITECTURE.md §1/§3) is
preserved; only the runtime artifact is trimmed.

Result: amy image lib 77 MB -> 48 MB (-38%), and all 4 Compose/skiko notary
dylibs gone (only secp256k1/jna/sqlite natives remain — the ones actually
loaded). A create-release.yml assertion fails the build if the UI stack ever
leaks back.

Verified with the SDK hidden + an offline amy command battery (init/whoami/
--json/relay/marmot/login, plus a real 6-relay key-package round-trip): zero
NoClassDefFoundError/linkage errors; init derives a secp256k1 key cleanly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015sso31DfSF9B6EFCVkEqWD
2026-06-18 01:39:08 +00:00
Crowdin Bot 468216ae1b New Crowdin translations by GitHub Action 2026-06-18 00:56:31 +00:00
Vitor Pamplona e1ec0b6823 Adds Bitcoin Sikho as a Designer to the contributors. 2026-06-17 20:51:08 -04:00
Claude 3c422b1824 ci(cli): surface notary log on non-Accepted; record signing validation
macOS validation (Developer ID D77MCV9NZ7) confirmed the hardened-runtime
entitlements are correct and load-bearing: amy init derives a secp256k1 key
cleanly, and dropping disable-library-validation reproduces the runtime
dlopen Team-ID failure. The one unverified gap is whether Apple's notary
service accepts the unsigned Mach-O dylibs embedded inside lib/*.jar
(secp256k1/jna/sqlite/skiko), which it inspects recursively.

- create-release.yml: the notarize step now submits with --output-format json,
  and on any non-Accepted status dumps `notarytool log` (per-file issues) and
  fails — so the first real run names the offending files instead of failing
  opaquely. No speculative in-jar signing yet; gather the log first.
- BUILDING.md: record the validation result, the embedded-jar-native risk, the
  one-run way to decide it (workflow_dispatch dry_run with MAC_* secrets), and
  the staged fixes (sign-in-jar and/or strip the skiko/Compose leak). Note the
  desktop app shares the same jars and needs its own dry-run.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015sso31DfSF9B6EFCVkEqWD
2026-06-18 00:49:26 +00:00
Claude d10e6c4d81 feat(cli): codesign + notarize the macOS amy tarball
Sign the macOS jlink image (amy-<version>-macos-arm64.tar.gz) so users who
download it directly clear Gatekeeper. Reuses the same Developer ID cert and
the six MAC_* secrets as the desktop DMG; no-op when they're absent.

- .github/actions/import-macos-cert: factor the throwaway-keychain cert import
  into a composite action; the desktop leg now uses it too (was inline).
- create-release.yml (build-cli macOS leg): import the cert, then codesign
  every Mach-O binary in the bundled JRE (executables get hardened-runtime
  entitlements, dylibs don't) and notarize via notarytool --wait. Runs before
  the collect step so the tarred image is signed. Job timeout 30->45 min for
  notarization headroom.
- cli/packaging/macos/amy.entitlements: hardened-runtime entitlements; the
  disable-library-validation key lets the JVM load the secp256k1 native dylib
  it extracts from a jar at runtime (would otherwise crash under notarization).
- BUILDING.md: document the tarball signing, the no-stapling/online-check
  caveat, and that the Homebrew-core jvm bundle is intentionally left unsigned.

Untested end-to-end (no macOS runner / Apple creds here) — validate with a
workflow_dispatch dry-run once the secrets are provisioned.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015sso31DfSF9B6EFCVkEqWD
2026-06-18 00:21:58 +00:00
Claude f24fc10212 feat(cli): publish no-JRE jar bundle + Homebrew-core formula for amy
Enable distributing the `amy` CLI via Homebrew-core (mainline formulae).
Homebrew-core builds in a network-sandboxed env, so a from-source Gradle
build can't resolve Maven deps there; the accepted pattern for JVM tools is a
pre-built no-JRE jar bundle + `depends_on "openjdk"`. installDist already
produces exactly that (bin/amy + lib/*.jar, no bundled runtime).

- create-release.yml: publish `amy-<version>-jvm.tar.gz` (the installDist tree)
  as a release asset on the linux leg. Pure JVM bytecode, so one
  platform-independent artifact serves every OS.
- packaging/homebrew/amy.rb: reference formula (depends_on openjdk, livecheck
  for BrewTestBot auto-bumps, `amy --help` smoke test). Not consumed by any
  build here — it's the artifact to submit to Homebrew/homebrew-core.
- BUILDING.md: homebrew-core submission runbook; note that the desktop app is
  already on mainline Homebrew (homebrew/cask); document name-collision and
  pre-built-jar review caveats.
- asset-name.sh: document the jvm bundle naming exception.

Verified locally: :cli:installDist builds with only a JDK (no Android SDK),
and the extracted bundle runs via JAVA_HOME (`amy --help` exits 0).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015sso31DfSF9B6EFCVkEqWD
2026-06-18 00:14:09 +00:00
Vitor PamplonaandGitHub 3a1cbd8c37 Merge pull request #3257 from vitorpamplona/claude/nice-maxwell-cb026o
Cashu: surface and help evacuate coins from untrusted mints
2026-06-17 19:55:50 -04:00
Vitor PamplonaandGitHub a700db6aa9 Merge pull request #3256 from vitorpamplona/claude/beautiful-shannon-75i3oc
Bump dependency versions in gradle/libs.versions.toml
2026-06-17 19:39:02 -04:00
Claude 644ccaedd5 chore(deps): bump stable dependency versions
Update to latest stable releases:
- Compose BOM 2026.05.01 -> 2026.06.00 (Compose core patch 1.11.2 -> 1.11.3)
- AndroidX Lifecycle 2.10.0 -> 2.11.0
- Firebase BOM 34.14.1 -> 34.15.0 (firebase-messaging 25.0.2 -> 25.1.0)
- google-services plugin 4.4.4 -> 4.5.0
- kotlin-test 2.3.21 -> 2.4.0 (align with kotlin 2.4.0)
- KSP 2.3.8 -> 2.3.9
- Spotless 8.6.0 -> 8.7.0

All stable, no breaking changes affecting Amethyst: no Navigation3 usage
(Lifecycle 2.11 nav decorator break N/A), Spotless pins ktlint 1.7.1 (no
reformat churn), and Firebase FCM deprecations are warnings only (no
allWarningsAsErrors). Verified: spotlessCheck, quartz compile + jvmTest pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PDjG6Lgp4SmGJ9vuD7XwDD
2026-06-17 23:19:27 +00:00
Claude c81dbb0127 feat(desktop): wire macOS Developer ID signing + notarization
Add gated code-signing + notarization for the macOS desktop DMG so it can
clear Gatekeeper and stay in Homebrew's main cask (unsigned casks are
rejected after 2026-09-01).

- desktopApp/build.gradle.kts: macOS signing{}/notarization{} blocks, gated
  on the AMETHYST_MAC_SIGN_IDENTITY env var. Absent => unsigned DMG, exactly
  as before, so local dev and PR CI are unaffected.
- create-release.yml: import a Developer ID cert into a throwaway keychain on
  the macOS leg and export the signing/notary env. Soft-gated on the
  MAC_CERTIFICATE_P12 secret — no secret => unsigned build.
- BUILDING.md: document the six MAC_* secrets, how to generate them, and flip
  the unsigned-cask fallback note to reflect the wiring is now in place
  (pending Apple credentials).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015sso31DfSF9B6EFCVkEqWD
2026-06-17 23:06:07 +00:00
Claude cca371c1f6 fix(cashu): recover from seed across held mints, not just configured
NUT-09 "Recover from seed" iterated only the configured kind:17375 mint
list, so funds at a mint dropped from the wallet config (while still
holding tokens) or auto-redeemed from a nutzap on an unconfigured mint
were silently skipped by recovery. Scan displayMints (configured plus any
mint we currently hold tokens at) instead.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TK5eNfhkNR1svcQxjY1JvR
2026-06-17 23:00:25 +00:00
Claude f9c8ce0213 feat(cashu): let users move coins off an unconfigured mint
Builds on the untrusted-mint highlight: the warning banner and each
flagged mint row are now actionable, opening an EvacuateMintDialog that
offers the three exits whose backends already exist —

- Move to a mint you trust: a new rebalanceOut() over the tested
  CashuWalletState.rebalance (mint-to-mint, no new Lightning sats). The
  amount is editable and defaults to the balance, with a hint that the
  Lightning fee is taken from the source so the full balance may not fit.
- Withdraw via Lightning: hands off to the existing Send-LN dialog.
- Export as Cashu token: hands off to the existing Send-token dialog.

The two Send dialogs now source from displayMints (not just configured
mints) and accept an initial mint, so they can be pre-pointed at the
mint being evacuated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TK5eNfhkNR1svcQxjY1JvR
2026-06-17 22:38:02 +00:00
Claude dbe1757ee9 feat(cashu): highlight balances held at unconfigured mints
Surface coins sitting at a mint the user never configured — almost always
auto-redeemed from a NIP-61 nutzap sent on a mint outside the recipient's
kind:10019. Until now such a balance counted toward the total and showed a
plain mint row, with nothing to tell the user it came from an unvetted
issuer.

- `CashuWalletState.unconfiguredMintBalances`: token-held mints minus the
  configured (kind:17375) set, keyed by mint URL -> sats.
- Wallet screen shows an error-styled recommendation banner when any exist
  and badges the offending mint rows ("Not in your wallet").

Informational first cut; the per-mint "move these coins to a trusted mint
or withdraw to Lightning" action (reusing rebalance / meltToLightning /
sendAsToken) follows separately.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TK5eNfhkNR1svcQxjY1JvR
2026-06-17 22:38:02 +00:00
Claude e1c3ebbab3 feat(cashu): surface all token-holding mints and sync them on wallet open
Two related gaps around nutzaps redeemed from mints not in the user's
configured kind:17375 list (e.g. a NIP-61 nutzap auto-redeemed from a
mint outside the recipient's kind:10019):

- The wallet screen's per-mint list iterated only the configured mints,
  so a token-only mint contributed to the total balance but had no row —
  the displayed per-mint balances under-counted the wallet. Add
  `displayMints` (union of configured + token-derived mints) so the rows
  sum to the full balance.

- Stale-proof reconciliation (`scrubLocallyStaleProofs`) only ran for the
  single mint a spend targeted, so proofs held at a non-configured mint
  were never checked until spent. Add `syncAllMints()` (an all-mint,
  non-destructive sweep) and wire it to the wallet screen opening via
  `CashuWalletViewModel.refresh()`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TK5eNfhkNR1svcQxjY1JvR
2026-06-17 22:38:02 +00:00
Vitor PamplonaandGitHub 4d7b88aa3f Merge pull request #3253 from vitorpamplona/claude/awesome-franklin-fhhs16
Cashu wallet: split recommendations into dedicated screen, add danger zone
2026-06-17 18:37:24 -04:00
Claude e8fdbc5532 fix(cashu): address pre-merge audit findings
- Invalidate the cached NUT-13 seed in applyEvents whenever the live kind:17375
  changes, so after a P2PK key rotation (recreateNutzapKey, or a rotation from
  another client) deterministic secrets re-derive from the new key instead of a
  stale cached seed. Removes the now-redundant reset in recreateNutzapKey.
- AccountSettings.updateNutzapInfo no longer backs up a mints-less kind:10019
  (the "stop receiving nutzaps" tombstone), clearing the backup instead — so the
  empty event round-tripping back through LocalCache can't undo clearNutzapInfo()
  and resurrect a withdrawn nutzap advertisement on next launch.
- Key keyMode's remember on isEditMode in AddCashuWalletScreen so a wallet
  delivered after first composition flips to KeepCurrent, preventing a silent
  key rotation on save in the cold-open race.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SXRAunSJS2dBx7B79qTMew
2026-06-17 22:32:15 +00:00
Claude d56f3a675d fix(cashu): keep Verify on current mints; reorder settings hub
- Restore the per-mint Verify button + reachability status on the already-added
  mints list. Verify is now in BOTH places: the current mints and the
  Matching/Popular suggestions (it was meant to be added to suggestions, not
  moved off the current list).
- Settings hub order: My mints → Mint recommendations → Recover from seed →
  Danger Zone, so the occasional recovery action sits last before the
  destructive section.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SXRAunSJS2dBx7B79qTMew
2026-06-17 22:32:13 +00:00
Claude c340fd05cf feat(cashu): reframe edit-wallet as mint editor; move Verify to suggestions
Adjusts the mint editor to the post-key-rotation reality and tidies its UI:

- Settings hub: "Edit wallet details / Mints, nutzap key" row becomes
  "My mints / Add or remove the mints your wallet uses." The edit screen title
  changes from "Edit Cashu wallet" to "Edit mints".
- The per-mint Verify button moves off the already-added mints list and into
  the Matching/Popular mints suggestion rows, sitting to the left of the +
  button, with the reachability result shown under each suggestion. Reuses the
  existing per-URL mintVerifications state.
- Fixes the Mint URL placeholder wrapping onto two lines (and inflating the
  field height) by capping it to a single ellipsized line.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SXRAunSJS2dBx7B79qTMew
2026-06-17 22:32:11 +00:00
Claude f970f92837 refactor(cashu): move mint recommendations to its own screen
The Cashu wallet settings screen becomes a thin redirector. The NIP-87 mint
recommendations management (add-input + autocomplete + own-list + retract
dialog) moves out into a dedicated CashuMintRecommendationsScreen, reached via
a new "Mint recommendations" nav row. Recover-from-seed and the Danger Zone
stay inline on the hub.

- New Route.CashuMintRecommendations + AppNavigation registration.
- New CashuMintRecommendationsScreen with its own top bar; carries the
  recommendation composables + previews that used to live in the settings file.
- CashuWalletSettingsScreen trimmed to nav rows + the recover action + the
  Danger Zone dialogs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SXRAunSJS2dBx7B79qTMew
2026-06-17 22:32:09 +00:00
Claude 6321780126 feat(cashu): move nutzap-key rotation to the Danger Zone
Splits the destructive P2PK key rotation out of the routine "edit wallet"
(mints) flow so editing mints can no longer accidentally orphan inbound
nutzaps.

- AddCashuWalletScreen: the P2PK key chooser now shows only at wallet
  creation. In edit mode the key is always kept (KeepCurrent), so saving
  mint changes never rotates the key.
- CashuWalletSettingsScreen Danger Zone: two new red, confirm-gated actions,
  each with a description of what it does and a note that it's rarely needed:
    * Recreate nutzap key — generate a fresh P2PK key.
    * Import nutzap key — adopt a pasted hex key (e.g. restore from backup),
      with inline validation/error surfacing.
- CashuWalletState.recreateNutzapKey / CashuWalletViewModel.recreateNutzapKey:
  re-publish kind:17375 + kind:10019 with a new/supplied key, keeping the
  current mint list, and invalidate the cached NUT-13 seed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SXRAunSJS2dBx7B79qTMew
2026-06-17 22:32:07 +00:00
Claude 7252726950 feat(cashu): add mints directly from browser, verify mints in the list
Edit Cashu wallet screen changes:

- The mint directory suggestions now carry a "+" button that adds the mint
  straight to the wallet's mint list, instead of an arrow that only copied the
  URL into the text field.
- Each already-selected mint row gets a small Verify button so the user can
  check reachability of mints they've already added (not just a freshly typed
  URL). Results are tracked per-mint via a new mintVerifications map on the
  view model, independent of the input-field ping state.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SXRAunSJS2dBx7B79qTMew
2026-06-17 22:32:04 +00:00
Claude f3ac2b14d1 style(cashu): make wallet settings danger zone red
Match the main Settings screen's danger styling: the "Danger Zone" header and
the Stop-nutzaps / Delete-wallet rows now use colorScheme.error for the header
text, row title, and leading icon. Reuses the shared R.string.danger_zone
instead of a duplicate cashu-specific string.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SXRAunSJS2dBx7B79qTMew
2026-06-17 22:32:02 +00:00
Claude b08f904ff3 test(geode): match renamed relay NAME "Geode" in NIP-11 assertion
Commit 270d229d renamed the relay's RelayInfo.NAME constant from "geode" to
"Geode" but left KtorRelayTest asserting the old lowercase value, failing the
pre-push test gate. Align the assertion with the source constant.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SXRAunSJS2dBx7B79qTMew
2026-06-17 22:32:00 +00:00
Claude 88a1755ae0 feat(cashu): add stop-receiving-nutzaps and delete-wallet actions
Adds two user-facing teardown options to the Cashu wallet settings:

- "Stop receiving nutzaps": replaces kind:10019 with an empty event (the
  durable signal, honored by every relay since it's a replaceable-event
  replacement) and then NIP-09 deletes it (best-effort, since deletions are
  optional on Nostr). The wallet and balance are untouched.
- "Delete wallet": withdraws the nutzap advertisement as above, then NIP-09
  deletes the kind:17375 wallet definition. Held kind:7375 proofs are not
  deleted (the ecash still exists at the mint), with a UI warning that any
  remaining balance / unredeemed nutzaps may become unrecoverable.

The on-disk backups of kind:17375 / kind:10019 are cleared when those events
are deleted, so a relaunch doesn't resurrect a deleted wallet from settings.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SXRAunSJS2dBx7B79qTMew
2026-06-17 22:31:57 +00:00
Vitor PamplonaandGitHub 93186a559c Merge pull request #3255 from vitorpamplona/claude/great-edison-nf92b4
fix(cashu): NUT-02 per-keyset input fees in melt/swap (fixes coinos send-LN)
2026-06-17 18:20:19 -04:00
Vitor PamplonaandGitHub 8282c5f609 Merge pull request #3254 from vitorpamplona/claude/beautiful-hawking-qfcq4w
Fix lone surrogates in truncated strings (emoji safety)
2026-06-17 18:15:37 -04:00
Vitor PamplonaandGitHub af9e2e8079 Merge pull request #3252 from vitorpamplona/claude/eloquent-fermi-4tpx8y
docs: add Zapstore metadata and publishing relay guidance
2026-06-17 18:08:21 -04:00
Claude ae678b219b docs(release-ops): swap relay.nostr.band for vitor.nostr1.com in zsp example
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VK27apdHs4Yzxa54qx44oJ
2026-06-17 22:07:13 +00:00
Claude 3ae9532efe fix: don't split surrogate pairs when truncating alt/summary tags
The NIP-31 "alt" summary for kind:1 notes was built with msg.take(50),
which counts UTF-16 code units. When the 50th unit landed between the two
halves of an astral character (e.g. the 🫡 emoji, U+1FAE1), it left a lone
surrogate at the end of the alt tag.

A lone surrogate is unencodable as UTF-8: it is kept in memory while the
event id is hashed (so the external signer signs that id), but it is
replaced by '?' the moment the event is serialized to a relay. Every relay
then recomputes a different id and rejects the event as having an invalid
id — making the affected note impossible to post.

Add a surrogate-aware String.takeKeepingSurrogatePairs() helper and route
TextNoteEvent's alt summary and the clink OfferClient description trim
through it. Adds regression tests covering the reported note and the helper.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HTHsaW6FVjvPqnrSGiT5ee
2026-06-17 22:04:19 +00:00
Claude d6e1af20de Merge remote-tracking branch 'origin/main' into claude/great-edison-nf92b4 2026-06-17 22:03:38 +00:00
Claude 2cbdfe749b refactor(cashu): migrate token-redeem melt to NUT-05
MeltProcessor (the "Redeem received cashu token → my Lightning address"
button) was hand-coded against the deprecated pre-v1 Cashu API (POST /melt
and POST /checkfees with {pr, proofs}). Those endpoints are gone on CDK and
other modern mints, and the path never accounted for NUT-02 per-input fees,
so it failed on fee-charging keysets the same way the wallet melt did.

Route it through the same NUT-05 CashuMintOperations the NIP-60 wallet uses:
requestMeltQuote + meltProofs. A probe quote at the full token value reveals
the LN fee_reserve, to which we add inputFeeFor(proofs) before fetching the
real invoice for (total − fees) and melting. No change is requested — there
is no wallet to hold leftover proofs, so the unused reserve stays with the
mint, matching the legacy behavior.

Supporting changes in CashuMintOperations:
- meltProofs gains requestChange (default true) so the redeem path can melt
  without minting orphan change outputs.
- inputFeeFor(proofs) exposes the per-keyset NUT-02 fee for invoice sizing.

Also drops the dead empty melt(...) overload that was a stub with a TODO.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Ffjz3doZ5CtFtAWSpvmqR
2026-06-17 21:42:31 +00:00
Claude 09e4d93e35 chore(zapstore): add icon + supported_nips and document publishing relays
Enrich the Zapstore listing with the 512x512 launcher icon and the full
supported_nips list (synced with the README checklist). Relays are not a
zapstore.yaml field — zsp reads RELAY_URLS (default wss://relay.zapstore.dev) —
so document how to publish to additional relays in the yaml and RELEASE_OPS.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VK27apdHs4Yzxa54qx44oJ
2026-06-17 21:41:54 +00:00
Claude d9a9ecdc05 fix(cashu): reserve the melt's NUT-02 input fee in send-LN swap-down
meltToLightning selects proofs covering amount+fee_reserve and, on
overshoot, swaps them down to exactly that before melting. But the melt
mints its inputs on the active keyset and the mint then charges its own
NUT-02 input fee on them — which the swap-down target didn't include. On a
fee-charging mint (mint.coinos.io active keyset = 100 ppk) the melt was
left a sat short and threw "Inputs total X < required Y", so fixing the
per-keyset swap fee alone just moved the failure from the swap to the melt.

Reserve activeKeysetInputFeeFor(required) on top of amount+fee_reserve for
both proof selection and the swap-down target so the subsequent melt has
room for its input fee.

Also fix the reported fee in MeltCompleted: the pre-paid swap "keep" was
split off before the melt and never spent, so subtract it instead of
counting the whole selected total minus change (which overstated fees by
the keep amount in the swap path).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Ffjz3doZ5CtFtAWSpvmqR
2026-06-17 21:28:50 +00:00
Vitor PamplonaandGitHub 5dc1e0ccf5 Merge pull request #3251 from vitorpamplona/claude/hopeful-cori-qpq0ug
Add music playlist composer screen and edit support
2026-06-17 17:25:42 -04:00
Claude 3b9cca19cf feat: add "Mine" option to music + playlist feeds; fix cover in editor
Two changes:

1. "Mine" top-nav filter for the Music and Playlists feeds. Adds a shared
   `musicRoutes` option list (the content-style catalog plus "Mine") to
   TopNavFilterState and points both music top bars at it. The local-cache
   feed filters and the relay sub-assemblers now handle TopFilter.Mine by
   restricting to the logged-in user's own tracks/playlists (by author, over
   their outbox relays) — same pattern as the badges/communities feeds.

2. Fix: editing a track/playlist showed the empty upload placeholder even when
   the event already had a cover. The shared CoverImagePicker now renders the
   already-published cover URL (with tap-to-replace and a remove button) when no
   new local file is picked. The track composer's clearPickedCover now also
   clears the saved URL so "remove cover" sticks on save.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013oofoSH7eMXrs2TCU4uncS
2026-06-17 21:14:55 +00:00