1
0
forked from GRIN/grim
Commit Graph

1193 Commits

Author SHA1 Message Date
2ro fc0999e271 nostr: opportunistic NIP-42 auto-auth for hardened DM reads
Enable the SDK's built-in NIP-42 auto-auth explicitly on the wallet's
relay-pool client so it answers a relay's AUTH challenge with a
signer-signed kind-22242 event, then re-issues the pending REQ. It fires
ONLY when a relay challenges (opportunistic): the wallet never forces
auth and never refuses a non-challenging relay, so it is inert against
today's non-challenging relays and only activates once the recipient-only
strfry fork starts challenging kind-1059 reads.

automatic_authentication defaults to true in nostr-sdk 0.44; set it
explicitly so a future default flip cannot silently break DM reads on the
hardened relay. Multi-identity is active-identity-only (single signer per
connection); the builder comment documents the full-multi-identity path
via the pool's forwarded AUTH challenge. Adds an inline test asserting the
opts+signer wiring.
2026-07-10 00:42:05 -04:00
2ro 0b5b072332 tor: drop dead privacy.over_tor/direct_connection i18n keys
The old privacy_ui used these two headers; this branch replaced it with
network_privacy_panels, which does not. Remove the now-unreferenced keys
from all locales (parity preserved, i18n_keys drift guard still green).
2026-07-10 00:42:05 -04:00
2ro 25e349a979 tor: translate per-user-tor strings, zh-Hant/HK auto-detect
Translate the 16 new per-user-Tor keys and the changed transport-neutral
goblin.privacy.intro across all 9 non-English locales (de/es/fr/ja/ko/ru/tr/
zh-CN/zh-TW), replacing the slice-3 English placeholders. Confirm
goblin.receive.copy was already translated everywhere.

Normalize zh-Hant, zh-HK, and zh-Hant-* system locales to zh-TW (and
zh-Hans(-*) to zh-CN) in setup_i18n, so Traditional-script systems that
don't report the exact zh-TW tag still land on Traditional instead of
falling through to the default locale.

Note the zh-TW -> zh news-locale fold in news_locale_code as an
intentional, low-priority tradeoff (Traditional readers see the same
zh news feed as Simplified); no new pipeline added.
2026-07-10 00:42:05 -04:00
2ro 8c8c75d18c tor: share nprofile on receive, Copy label, centered goblin mark on QR
- receive Share/Copy now emit the nprofile (relay hints) so payers learn
  which relays to reach the recipient on; in-app npub still tracks who-is-paid
- rename receive button 'Copy npub' -> 'Copy' (goblin.receive.copy) in all
  10 locales; copies the nprofile the QR already encodes
- composite the black goblin mark centered on the receive/my-code QR
  (widgets.rs, High ECC) and the static qr.rs display (bumped to High ECC)
2026-07-10 00:42:05 -04:00
2ro 346422003f tor: network-privacy UI, blueviolet token, big switch + status wiring
Phase 2 slice 3 of the per-user-tor plan: the interactive UX layer on top of
slices 1-2 (wallet-level Tor setting + transport-aware relay selection).

Colors:
- Add a tor_purple token (CSS blueviolet #8A2BE2) to ThemeTokens in all three
  theme consts, exposed via Colors::tor_purple().

Shared Network-privacy component (onboarding + settings never drift):
- network_privacy_panels(): intro copy (what Goblin sends + how privacy works,
  no crypto version numbers), a Grin-node ALWAYS-DIRECT panel (grey dot, full
  width), a private-traffic panel whose header is TOR OFF (red) / TOR ON (green)
  and whose status dots are goblin-yellow when off / blueviolet when on, a large
  Tor switch (w::toggle_large: dormant gray off, blueviolet on, never yellow),
  and a VPN nudge. Repurposes the old read-only privacy_ui.

Onboarding:
- New Step::Privacy rendered before wallet creation; the create/restore triggers
  route here first. New users default OFF. The choice is stashed on
  OnboardingContent and written as the new wallet's EXPLICIT tor setting
  (set_tor_enabled) right after open() (init_nostr builds the service
  synchronously; start() runs later on the sync thread), so a new wallet writes
  an explicit value while upgraders keep None -> Tor ON.

Settings:
- Tor routing row shows On/Off and opens the Network-privacy screen; the big
  switch there flips the wallet tor setting (set_tor_enabled + set_route_over_tor)
  and restarts the service so the pool rebuilds on the new transport.

Status lines:
- Rewire the 3 Tor-only status call sites (home card, Me identity row,
  onboarding identity) to service.transport_status(), adding Connected/
  Connecting (direct) states so a clearnet wallet no longer reads as forever
  connecting over Tor. Fix the Me-row repaint loop to gate on relay-connected
  rather than the Tor-only transport_ready.
- Re-export TransportStatus from crate::nostr (slice-1 miss).

i18n: 16 new keys added to all 10 locales (English placeholder in the 9
non-English files; a following slice translates them).
2026-07-10 00:42:05 -04:00
2ro d48cad5e6a tor: transport-aware relay-set selection (fixed Tor pin vs per-identity clearnet subset)
On Tor, every identity uses a FIXED pinned set (relay.floonet.dev +
relay.nostr.net + offchain.pub). On clearnet, each identity keeps its
persisted random healthy subset (dm_relays), floonet pinned. Route
NostrService::relays() through a new pure effective_relays() resolver
and gate ensure_advertised_set() to clearnet so Tor never overwrites the
persisted clearnet subset. relay.floonet.dev is pinned first in both
regimes. Adds unit tests for the resolver.
2026-07-10 00:42:05 -04:00
2ro 3c3b680f93 tor: per-wallet Tor setting + reintroduce the clearnet transport/HTTP path
Foundation slice for making Tor a wallet-level user setting (Phase 2, slice 1
of the per-user-tor plan). Transport/config only; no onboarding/settings UI,
relay-set swap, or nprofile in this slice.

Wallet-level tor_enabled (tri-state):
- NostrConfig gains tor_enabled: Option<bool> with the existing Option +
  serde(default) pattern. tor_enabled() resolves None -> ON, so every legacy
  nostr.toml keeps Tor with no migration; new wallets write an explicit value at
  onboarding (later slice). Adds tor_enabled_is_set() + set_tor_enabled() and a
  back-compat/tri-state unit test.

Clearnet path (the big lift, rebuilt after the Nym removal took it out):
- ClearnetWebSocketTransport parallel to TorWebSocketTransport: direct TLS
  websocket, honoring the user's AppConfig SOCKS5 proxy (tokio-socks, already in
  the tree) so a VPN/proxy user can front their traffic. Shares ws_config/
  split_ws with the Tor transport.
- Transport selection moved into run_service: the wallet's resolved tor_enabled()
  picks Tor vs clearnet when the one pool is built; the Tor-bootstrap wait is
  skipped for clearnet wallets. restart() re-enters here, so a settings toggle
  rebuilds the pool on the new transport.
- Process-global route flag (tor::set_route_over_tor/route_over_tor), mirrored
  from the open wallet like set_home_domain, so free-function HTTP callers pick
  the matching transport off-thread.
- tor::http_request_bytes takes a clearnet branch when the wallet is Tor-off
  (no more hard-fail on "Tor not bootstrapped"); new http::clearnet_request_bytes
  reuses the existing HttpClient (proxy-aware) with the same redirect behavior
  and browser-like UA. NIP-05, price, relay-pool gist and NIP-11 probes all ride
  it unchanged.

Status/readiness:
- New TransportStatus enum + NostrService::transport_status()/tor_routing():
  exposes a "Connected (direct)" clearnet state so a Tor-off wallet does not read
  as "connecting over Tor" forever. The three UI status lines are rewired to it
  in a later slice; the state is exposed cleanly now.

Grin node path unchanged (stays clearnet always). cargo check --all-targets
green; targeted config/nostr/tor tests pass.
2026-07-10 00:42:05 -04:00
2ro d7ec692d03 pay: remove top-right tap-to-settings avatar from pay page
Build157 put GoblinView::avatar_self (a tap-through to Settings) top-right
on both the home and pay headers. Per owner request, drop it from the PAY
page only; the home header avatar is unchanged. The scan-QR glyph and the
rest of the pay layout are left in place. Settings remains reachable from
the bottom nav bar (Tab::Me) and the wide-mode sidebar.
build159
2026-07-08 02:22:53 -04:00
2ro 9bdc0e2c60 locales: apply firm zh-CN audit corrections + mirror to zh-TW
Fix 21 keys (23 substitutions) flagged in the zh-CN Goblin-string audit,
and apply the equivalent Traditional corrections to the matching zh-TW keys
so both stay correct and consistent:
- 7 leftover-English strings (you/username/handle/kind) translated
- 3 stray formal 您 -> informal 你 (plus Nostr -> nostr where en is lowercase)
- 7 terminology tweaks (机构->授权方, kind->类型, 活动->动态, 费用->手续费)
- 4 half-width -> full-width commas
Judgment-call rows (review_btn/title, pairing.title) intentionally not applied.
2026-07-08 02:22:53 -04:00
2ro e623271476 locales: add Traditional Chinese (zh-TW)
Full native-quality Traditional Chinese (Taiwan) locale covering all 910
keys. Shared GRIM-inherited keys use jasperli2026's zh-TC values verbatim;
goblin.* namespace translated from the corrected Simplified source into
Taiwan-idiom Traditional (OpenCC s2twp plus a manual Taiwan post-fix and
review pass), keeping Jasper's voice and crypto-wallet register. Cangjie
keyboard labels reused from zh-CN; lang_name set to 繁體中文.

Wired into the i18n drift test (OTHER_LOCALES) and README attribution;
rust_i18n auto-loads it and the language picker labels it via lang_name.
System locale zh-TW auto-selects; news folds zh-TW to zh (shared with
Simplified) as noted in news_locale_code.
2026-07-08 02:22:53 -04:00
2ro 8bbb7853ef locales: adopt corrected Simplified Chinese from jasperli2026 (keep Cangjie keyboard + Goblin title) 2026-07-08 02:22:53 -04:00
2ro f2781fb5a9 locales: fix zh-CN lang_name to native 简体中文
The Chinese locale self-label read 英语 (the word for "English"). Use the
native Simplified Chinese name so the language picker row is correct.
2026-07-08 02:22:53 -04:00
2ro 52e8ff6236 settings: clear Android bottom inset so last button is not clipped
Append ui.add_space(View::get_bottom_inset()) at the end of
SettingsContent::ui. The settings screen supplies no bottom inset (its
tab panel is hidden and the CentralPanel frame uses bottom:0), so the
final FADERS Settings button clipped under 3-button nav / the gesture
pill. get_bottom_inset() is 0 off Android, so desktop is unaffected.
2026-07-08 02:22:53 -04:00
2ro 1521b22cdd nostr/pool: remove inert co-located-exit scaffolding
Drop the PoolRelay.exit field and the exit_for/exit_for_host/has_exit
helpers left over from the retired Nym-era co-located scoped exit. The
deleted src/nym was their only real caller; the Tor build never consumes
them. PoolRelay has no deny_unknown_fields, so a gist that still carries
a stray per-relay "exit" key keeps deserializing and the key is ignored
(covered explicitly in the unknown-fields test).
2026-07-08 02:22:53 -04:00
2ro 35c9ea3bbc Remove dormant Nym/mixnet transport, commit fully to Tor
The wallet pivoted from the Nym mixnet to Tor in build134; the Nym code
had been left on disk behind an optional, always-off "nym" feature and
never ran since. Delete it entirely: no Nym/mixnet fallback and no
Nym/mixnet mentions remain. Tor is the sole transport.

- Delete src/nym/ (mod, transport, streamexit, dns, nymproc) and its
  `#[cfg(feature = "nym")] pub mod nym;` declaration in lib.rs.
- Cargo.toml: drop the `nym` feature and the commented-out Nym-only
  path deps (nym-sdk, smolmix, hickory-proto); rewrite the mixnet-era
  comments on the rustls/tokio-rustls/arti/openssl deps to the current
  Tor reality. No active dependency changed: rustls, tokio-rustls and
  webpki-roots stay (used by the Tor HTTPS client in tor/mod.rs).
- settings/config.rs: remove the persisted nym_entry_gateway /
  nym_last_ipr fields and their getters/setters. No serde
  deny_unknown_fields, so existing on-disk configs that still carry
  those keys keep deserializing (the removed keys are ignored and
  dropped on next save; same path the price-cache-removal test guards).
- Rename the Nym/mixnet-named i18n keys (values already said "Tor")
  across all 9 locales and their t!() call sites: connected_nym ->
  connected_tor, nym_ready -> tor_ready, connecting_nym ->
  connecting_tor, mixnet_routing -> tor_routing, over_mixnet ->
  over_tor. Displayed strings unchanged.
- Rewrite the Nym/mixnet-referencing comments in the active Tor files
  (tor/engine.rs, tor/mod.rs, nostr/client.rs, nostr/pool.rs,
  nostr/mod.rs, lib.rs, wallet/wallet.rs, node/node.rs,
  gui widgets/mod/onboarding, Android BackgroundService) to describe
  Tor; drop the now-broken [crate::nym::*] intra-doc links. No active
  code behavior changed.

The pool's `exit` schema slot and exit_for/exit_for_host/has_exit
helpers are kept (comments de-Nym'd): they are inert under Tor but let
a pool document that carries an exit still parse.
2026-07-08 02:22:53 -04:00
2ro 079fdd4841 docs: README default relay set matches code (Tor-friendly relays, drop damus/nos.lol) build158 2026-07-07 19:21:05 -04:00
2ro 4337580806 goblin: full wallet backup (seed + all identities) with restore
The .backup file now seals the money seed AND every held identity in one
encrypted, versioned envelope, and the caption's promise ("Contains your
wallet and all identities.") is finally true.

Format (nostr/identity.rs): a JSON envelope tagged goblin_backup:2. The
seed phrase is sealed with seal_secret_text() and each identity with the
existing per-identity to_encrypted_backup() (so every element is itself a
valid v1 identity envelope). Both layers reuse the in-tree NIP-49 ncryptsec
+ NIP-44 primitives under one password: no new crypto, no new dependency.
The plaintext seed is assembled and sealed in memory (the zeroizing
recovery string drops at scope end) and never written to disk. Old v1
single-identity backups keep restoring unchanged; is_full_backup() is
checked before the v1 path so the two never collide.

Build (wallet.rs): create_full_backup() gathers the seed via the existing
recovery API (which proves the wallet password) plus every unlocked held
identity. restore_full_backup_identities() reinstates them into a freshly
created wallet, re-encrypting each under the new wallet password, making the
backup's active identity active, and reusing the held-index/service-rebuild
plumbing. The identity-only import paths now reject a full backup with a
clear pointer to wallet creation.

Restore (onboarding.rs): the restore-from-seed step gains a .backup picker;
unlocking it decrypts the seed into the standard 24-word creation grid and
stashes the identities, which a worker reinstates once the wallet opens.

Copy: backup caption, title, blurb, and saved-sub updated across all nine
locales; two new restore strings added in all nine.

Tests: full_backup_roundtrips_seed_identities_and_active,
old_single_identity_backup_is_not_a_full_backup,
seal_secret_text_roundtrips_and_is_opaque; i18n drift + news tests green.
2026-07-07 19:21:05 -04:00
2ro 5ea549abf4 goblin: restructure Advanced page into Advanced Nostr Settings + Danger Zone
Group the Advanced settings page under two grey/red section kickers:

- ADVANCED NOSTR SETTINGS: the Nostr key, and directly below it the
  single always-everything .backup download (one button, no checklist).
  Moved the .backup off the main Settings identity card so it has one
  home (no duplicated exposure). Caption states what the file truly
  holds: the current identity's key and username.
- DANGER ZONE (red kicker): Delete wallet, now password-gated. The old
  tap-twice confirm is replaced by a wallet-password gate
  (get_recovery), with an inline "Download backup" button before the
  password field wired to the same seal action. Copy states the delete
  also removes every identity and urges a backup first.

Add w::kicker_danger; BackupState.anchor_delete routes the seal form to
whichever section opened it. New strings in all nine locales; clear the
backup password from memory on leaving the page.
2026-07-07 19:21:05 -04:00
2ro 5fecba6ff5 goblin: make the self top-right avatar mode-aware (anon-only yellow)
Build 157 hard-coded the flat Goblin-yellow censored tile for the user's
own top-right avatar on the home and pay headers, regardless of anonymous
mode. The owner clarified: anonymous mode is the ONLY thing that turns an
avatar yellow.

Replace the mode-blind w::avatar_self widget with a mode-aware
GoblinView::avatar_self helper: anon ON renders the censored yellow tile
(w::avatar_censored), anon OFF renders this identity's normal gradient/
picture identicon (w::avatar_any), matching every other surface. Remove
the now-unused free avatar_self widget from widgets.rs.

All other avatar_censored call sites (identity switcher, activity rows)
remain correctly gated behind the anonymous-mode flag.
2026-07-07 19:21:05 -04:00
2ro debad91e29 nostr: raise news cap to 18 so English variant survives multi-language batches
A single news post now ships as nine per-`d` language variants and the
publisher emits English first, giving it the oldest `created_at` in the
batch. With the store capped at 8 events across all d-tags, the English
event was always the 9th-newest and got evicted — English readers saw no
news. The catch-up/subscription filter also capped fetches at 4, so fresh
installs only ever received the newest four events.

Raise NEWS_CAP from 8 to 18 (two full nine-language posts) and bump the
news filter limit from 4 to 18 to match. Selection logic is unchanged.
Extend the reconcile_news test with a nine-variant batch that proves the
untagged English event survives under the real cap.
2026-07-07 19:21:05 -04:00
2ro 567c054fe6 goblin: drop the Activity top-right self avatar (owner veto)
Activity returns to a plain title with no top-right avatar; the flat
yellow Grin-mark self avatar stays on the Home and Pay headers only.
build157
2026-07-07 14:22:46 -04:00
2ro 54af417ec0 goblin: Grin-mark anon/self avatars + Export archive caption, archive under Advanced privacy
Anonymous-mode censored avatar now composites the Grin mark (the same path,
90% scale, and 67%-black ink a normal gradient avatar draws) over the flat
#FED60E tile instead of the Goblin head. The user's own top-right avatar on
the Home, Pay, and Activity surfaces always renders as that same flat yellow
+ Grin-mark tile (new avatar_self), never a picture or per-identity gradient;
Activity gains the settings avatar to match Home and Pay.

Export archive gains a caption describing exactly what it copies (contacts,
payment history, and requests, to the clipboard as JSON), localized across all
nine locales. The standalone Archive section is removed from the main settings
page; Export archive and Wipe payment history now live inside Advanced privacy.
2026-07-07 14:22:46 -04:00
2ro 51de695d4e ja: align with removed rotate/import keys + learn_more build156 2026-07-07 01:49:58 -04:00
2ro f39785096d goblin: revert Pay header to original goblin mark
Restore the Pay tab header top-left rendering to the original goblin
mark (goblin-logo2.svg tinted to the header ink at 40px), reverting the
GoblinPay badge/wordmark theme-split lockup introduced by f768907 (UI
pack 153, merged via build154). Matches the owner reference: the plain
black goblin girlhead silhouette on the yellow header, no badge, no
wordmark.

The two badge assets added by that commit are no longer referenced
anywhere after the revert, so remove them:
  img/goblinpay-badge-black.svg
  img/goblinpay-wordmark.svg

Scope is strictly the Pay header hunk; the Home wordmark sizing from the
same UI pack is untouched.
2026-07-07 01:49:48 -04:00
2ro 705b954fe9 es: fix handle-loanword and verb-form inconsistencies
Full read-through of the just-landed es and ko locales against en.yml.
ko needed no changes. es had three spots that drifted from its own
established style:

- share_handle / n05_blurb translated "handle" as "identificador(es)"
  while every other occurrence in the file (search_hint, no_contacts,
  scan_not_recipient, enter_recipient) keeps it as the loanword
  "handle" — switched these two to match, consistent with the
  fr/de/tr precedent of treating "handle" as an untranslated loanword.
- scan_to_pay_me used the imperative "Escanea" while every sibling
  label (scan_to_pay, scan_to_request, tab_scan, scan_qr) uses the
  infinitive "Escanear" — aligned it to match.
2026-07-07 01:49:48 -04:00
2ro 78c5667a18 Add Japanese (ja) as ninth Goblin wallet locale
Full translation of all 918 goblin.* leaf keys from en.yml, byte-matching
key order and %{...} placeholders, standard polite desu/masu register.
House terms follow the es/ko precedent: Goblin/GoblinPay/GRIM/Tor/Nostr/
slatepack/npub/nsec stay untranslated; wallet = ウォレット, recovery
phrase = リカバリーフレーズ, identity = アイデンティティ, relay = リレー,
handle stays as a katakana loanword (ハンドル) throughout.

Font: verified glyph-by-glyph against fonts/noto_sc_reg.otf (already
wired into every font family stack) that it covers every character used
in ja.yml, including hiragana/katakana and kokuji kanji not used in
Chinese, so no new Japanese font file is needed.

Keyboard block keeps literal QWERTY romaji like en/es/fr/de: the custom
on-screen keyboard widget is dead code in this fork (no_soft_keyboard is
always true; native OS keyboards handle all text entry), so there is no
functional need for a kana layout, per zh-CN precedent review.

Registers ja in tests/i18n_keys.rs OTHER_LOCALES and refreshes locale
count/list comments in src/lib.rs, src/gui/views/goblin/mod.rs and
src/gui/views/goblin/data.rs.
2026-07-07 01:49:48 -04:00
2ro befff36133 Fix Sign in with Goblin trust announce over Tor
The session-open announce that tells the site a trust session exists was
confirmed on the wrong signal and never retried, so the live flow either
toasted announce_failed or returned to the browser without logging in.

Root causes:
- Confirm-on-any-relay: announce_new_sessions marked the grant confirmed
  when ANY wallet relay accepted the session-open (out.success non-empty).
  The site (magick) subscribes ONLY on its r= hint relay, so an accept on
  another wallet default let the wallet "succeed" and return-to-caller
  while the site never saw the session. Now confirm requires a relay the
  site actually listens on (the hint) via announce_confirmed().
- Announced-set-unconditionally: s.announced was set true even when the
  publish failed, and nothing re-armed sessions_dirty, so a failed announce
  was never retried and announced_ok stayed empty forever. announced now
  flips true only on a real hint-relay confirm; the loop re-arms and
  re-publishes each tick until confirmed or ANNOUNCE_MAX_ATTEMPTS, tracked
  by a new Session::announce_attempts counter.
- Relay dedup/normalization: channel_relays deduped hints by raw string, so
  a trailing-slash or case difference (wss://host/ vs wss://host) forked a
  phantom cold relay and broke confirm matching. canonical_relay() now folds
  both the union (dedup_relay_union) and the confirm comparison.
- Deadline too short for a cold Tor circuit: TRUST_ANNOUNCE_TIMEOUT_SECS
  raised 15s -> 30s to cover reaching a relay we were not already dialed to.

Tests: canonical_relay/dedup, hint-relay confirm semantics, retry cap.
cargo test --lib 246 passed (242 baseline + 4), i18n_keys green, fmt clean.
2026-07-07 01:49:48 -04:00
2ro e3b4545dc4 goblin: remove Settings Import identity (replace-current) flow
Owner correction: importing an existing identity belongs at first-run
(onboarding, restored in the previous commit), not in Settings. The
Settings > Identity "Import identity" row was the destructive
replace-the-current-identity action, redundant now that add-identity and
the identity switcher cover bringing in keys non-destructively. Removes
the settings row, the inline ImportState/import_nsec_ui flow, and the
five now-unused settings keys (import_identity, import_identity_title,
import_blurb, identity_replaced, now_using) across all eight locales.

Wallet::import_nostr_identity stays: the onboarding import panel calls
it. The shared settings.* strings the onboarding panel reuses
(importing, import_failed, import_nsec_hint, backup_read_failed,
choose_backup_file, backup_password_hint, import_btn, cancel, close)
also stay. Drift + call-site tests green.
2026-07-07 01:49:48 -04:00
2ro 26d3d41790 Revert "goblin: remove the Import identity option from onboarding"
This reverts commit b1baa14b85.
2026-07-07 01:49:48 -04:00
2ro 811eff825c goblin: remove the rotate-nostr-identity feature
Rotation just does Keys::generate (IdentitySource::Random) — the old
seed-derived path was dropped in Build 8 — so it is functionally a random
add-identity-and-switch, and it does NOT carry the username or profile
over (it RELEASES the old name and hands the new key a nameless identity).
It therefore adds nothing over the identity switcher, so remove it: the
Settings entry row, the inline rotate flow (RotateState + rotate_ui) and
the now-unreachable Wallet::rotate_nostr_identity. Shared helpers
(unlock_all_identities, create_random, nip05::unregister) are untouched.
Drops the fifteen unused goblin.settings.rotate_* / key_rotated /
new_npub / backup_new_key / copy_new_nsec / rotation_failed keys from all
eight locales; drift + call-site tests green.
2026-07-07 01:49:48 -04:00
2ro 44f9363ecf goblin: remove the Import identity option from onboarding
Onboarding import replaces the just-created identity and is risky, so the
owner ruled it out of the first-run flow; returning users import via the
normal identity-management flow in Settings instead. Removes both entry
points added earlier (the wallet-step import chip and the identity-step
centered button) and the now-unreachable onboarding import sub-flow
(OnbImport state + import_ui), leaving the Settings import path fully
intact. Drops the unused goblin.onboarding.wallet.import_identity{,_hint}
and goblin.onboarding.identity.import_{existing,title,blurb} keys from all
eight locales; drift + call-site tests green.
2026-07-07 01:49:48 -04:00
2ro 3b80650972 goblin: shorten Username name-authority copy + add Learn more link
Replace the long name-authority blurb on the Username page with one brief
line, plus a "Learn more" link that opens the name-authority docs chapter
(https://docs.goblin.st/features/name-authority.html) via the same
open_url idiom used elsewhere in Settings. Authority list, free-type and
save behaviour are unchanged. New goblin.username.learn_more key and the
shortened authority_blurb across all eight locales; drift + call-site
tests green.
2026-07-07 01:49:48 -04:00
2ro bb1023e51b Anonymous mode: censored avatars + Recent strip
While anonymous mode is on, replace every counterparty avatar on the
wallet home and activity panels with one uniform censored tile: a solid
Goblin-yellow (#FED60E) circle with the Goblin mark inked dark on top
(reusing the img/goblin-logo2 asset that widgets_logo draws). Identical
for every identity, so no picture, gradient, or initial leaks.

The Recent strip is now anonymized like the rest of the surface: the
censored tile for every avatar and dotted names. Tap-to-reveal is intact
(the row/tile still senses clicks and opens the full detail).

Shared CENSOR_NAME_DOTS backs the activity title and the Recent names.
Adds a unit test that the censored name is dots-only.
2026-07-07 01:49:48 -04:00
2ro c3bd0c00d0 android: sign release build type from keystore.properties when present
The release build type now applies signingConfigs.release when
android/keystore.properties exists, and stays on the debug-signed
fallback when it is absent, so existing local and CI builds are
unchanged. Also ignore *.jks so signing keys can never be committed.
2026-07-07 01:49:48 -04:00
2ro 773589a2f1 Add Spanish and Korean locales (es, ko)
Translates every en.yml key into es.yml and ko.yml (918 keys each,
placeholder-parity verified), wires both into the drift/call-site test
(tests/i18n_keys.rs), and bundles a Hangul font subset (noto_kr_reg.otf,
~1.9MB, Noto Sans CJK KR Regular subset to Hangul + Jamo + Latin/CJK
punctuation) as a proportional/monospace fallback so Korean UI text
actually renders instead of showing tofu boxes. The Chinese font already
covers Han but not Hangul, which is a disjoint Unicode block.

Language registration itself needed no code changes: rust_i18n::i18n!
auto-discovers yml files in locales/, the locale-detection fallback in
setup_i18n already matches on bare subtags generically, and the language
picker iterates rust_i18n::available_locales!() and renders each
locale's own lang_name key (Espanol, and Hangul for Korean).
build155
2026-07-06 23:09:45 -04:00
2ro 94951e8ca9 Username page: name authority on top, claim below
Swap the two panels on the Username settings page so the flow reads
top-to-bottom: pick your name authority first (known list + custom
save, default goblin.st), then claim or release your name on it.

Pure reorder of username_ui; no behavior or copy changes.
2026-07-06 23:09:45 -04:00
2ro f72a9718a3 goblin: move Trusted Sites into the nostr settings group + fix raw-key leaks
Trusted Sites is nostr-identity signing, and name authority now lives on the
Username page, so the nostr cluster is its logical home. Pure placement move:
the row now renders inside the identity card just below Nostr Relays; label,
live session-count value, and open handler are unchanged.

Also carries the two raw-key leak fixes (goblin.receipt.copied ->
goblin.receive.copied, wallets.canceled -> wallets.tx_canceled) and the
every_t_call_site_key_exists_in_en guard, so this branch is clean on its own.

Translations for the new settings/username/advprivacy keys were reviewed
across all six locales and left as-is (consistent with house register).
build154
2026-07-06 22:11:00 -04:00
2ro 7223e65a7a goblin: promote "Import identity" to first-class onboarding buttons
The returning-user identity-import path (a .backup file or a bare nsec,
existing plumbing since Build 92) is now surfaced as proper centered
buttons instead of a small left-aligned text link.

- Wallet step: a first-class "Import identity" choice stands alongside
  Create new / Restore from seed. It composes with either seed choice; the
  import panel itself opens on the identity step once the wallet exists.
- Identity step: the left-aligned "Already have an identity? Import it"
  link is replaced by a centered secondary button labelled "Import
  identity", consistent with the other onboarding buttons. The panel behind
  it clearly offers BOTH a .backup file picker and an nsec field.

New keys goblin.onboarding.wallet.import_identity{,_hint} in all six
locales; drift test green.
2026-07-06 22:11:00 -04:00
2ro 4a91998a44 goblin: settings polish (wrap fix, 5-dot censor, nav reset)
Follow-ups on the Settings redesign, all in the goblin view:

- Truncation sweep: settings_row_toggle now reserves room for the switch
  and bounds the text column, so a long label/subtitle WRAPS to another
  line instead of running under the switch and clipping. Fixes the
  Advanced Privacy notification descriptions (worst in longer locales);
  applies uniformly to every toggle row. The Username and Advanced Privacy
  blurbs are full-width labels that already wrap. No description on the new
  surfaces can truncate: wrapping is width-bounded, not length-tuned.

- Anonymous-mode censor is a FIXED count of dots (five), computed by a pure
  censored_amount_dots() that ignores the real amount, so the balance
  magnitude never leaks. Used for both the home balance hero and censored
  activity amounts. Unit-tested to never vary with size.

- Settings navigation resets to the root whenever the user is off the
  Settings (Me) tab, so leaving and re-entering always lands on the
  top-level page instead of the last sub-page. One reset at the leave/enter
  boundary via a pure settings_page_after(); deep links that open a
  sub-page set the tab to Me in the same frame, so they survive. Unit-tested.
2026-07-06 22:11:00 -04:00
2ro 6af8fd1c26 goblin: Settings redesign (Username + Advanced Privacy pages)
Two new Settings pages, each the single home for its feature.

Username (SettingsPage::Username) consolidates everything name-related
off the main Settings and out of the identity card: claim a name if you
have none, release the one you own, and choose the name authority from a
known list OR a free-typed custom server. Reuses the existing claim_ui and
claim/release worker flow; the old inline name-authority editor and the
main-settings claim card are removed (single home).

Advanced Privacy (SettingsPage::AdvancedPrivacy) replaces the lone
"hide amounts" toggle. Notifications section: hide amounts (existing),
hide names, or hide all details (a generic localized private alert that
leaks no name or amount; an empty amount collapses the Android
notification template to just the private line). Anonymous mode: one
toggle that censors STRICTLY the home balance (dots, tap to reveal; the
fiat rate fetch stays off until revealed) and the activity list (dots for
names and amounts, tap a row to reveal and open). Presentation only, with
no money-path or storage change.

Migration: the existing hide_amounts config keeps its exact meaning
(notification amount hiding); the three new fields default off, so no
surprise on upgrade. Covered by a new config test (227 lib tests).

All new strings via t!() in all six locales; drift test green.
2026-07-06 22:11:00 -04:00
2ro f7178254e0 goblin: complete the flow BEFORE return-to-caller + terse trust prompt
Root cause of the broken build 153 QR trust login, confirmed in code:
cb.return_to_caller() fired in the sign handler right after spawning the
callback POST worker. The app backgrounded immediately, the GUI frame pump
stopped, and the rest of the grant lives on that pump: the outcome poll that
consumes the POST result, the session creation, and the session-open channel
publish (which the service loop only sends on its next tick, after
add_session marks the set dirty). Backgrounded, none of that ran, so the
site never received the session and the browser poll never logged in.

The ordering fix, uniform across login, authorize, and trust:
- the sign handlers no longer return-to-caller; the app stays foreground
  (frames pumping) until the flow is fully complete
- login/authorize: the return decision happens in the outcome poll, only on
  a successful POST; a failed POST keeps the user in the wallet with the
  honest toast
- trust: the decision waits further, for the session-open announce to be
  CONFIRMED handed to a relay. announce_new_sessions now records per-channel
  delivery (at least one relay accepted) in NostrService::session_announced;
  the new TrustWait GUI state polls it with a 15s deadline and an honest
  "announce unconfirmed" toast on timeout (goblin.trust.announce_failed x6)
- a freshness bound on all three: a result consumed on a late resume (the
  user backgrounded the wallet themselves mid-flow) shows the toast but
  never bounces them back out
- the decision itself is the pure authuri::should_return_to_caller(allowed,
  post_ok, publish_confirmed), unit-tested: pending, failed, or unconfirmed
  work never returns; rt=0 never returns

Trust prompt copy, owner directive: the gist in one or two lines, detail
tucked away. The modal now leads with a single plain line (act-for-you plus
the money rule) and folds categories, money line, and duration behind the
existing show-full disclosure idiom; the red caution lines stay visible.
goblin.trust.* keys tightened in all six locales, drift test green.
2026-07-06 22:11:00 -04:00
2ro 1f224107a0 goblin: gate return-to-caller on an optional rt flag (QR scans stay in-wallet)
The Build 151 return-to-caller is right for same-device deep-links but wrong
for a QR scanned from another screen: there is no caller, so the wallet bounced
the user into whatever app was previous. The login/authorize/trust URIs now
carry an optional rt flag: rt=0 disables the return (QR case), rt=1 or an absent
flag keeps the return (backward compatible). Parsing is byte-exact and fails
closed to the return-enabled default on empty, garbage, or duplicate values
(first occurrence wins, like every other param). Security is unchanged: the flag
is a single boolean gate on the existing best-effort app-switch and derives no
target, so it can never introduce a redirect the URI could not already perform;
cb-host==domain binding and every other check are untouched.

- shared parse_return_flag helper in authuri, reused by login and trust
- return_to_caller field on LoginUri, AuthorizeUri, TrustUri (default true)
- all three GUI flows (login, authorize, trust) honour the flag uniformly
- parser unit tests: absent/explicit/garbage/duplicate + the no-return decision
2026-07-06 22:11:00 -04:00
2ro d24a09074a goblin: polish FR stale label + fix raw-key leaks on this branch
FR wallets.tx_stale was 'En attente trop longtemps' (noun + adverb, not
grammatical); now 'En attente trop longue', adjective agreeing with attente.
Other five locales for tx_stale / receipt.stale_note read natural, left as-is.

Also carries the two raw-key leak fixes (goblin.receipt.copied ->
goblin.receive.copied, wallets.canceled -> wallets.tx_canceled) and the
every_t_call_site_key_exists_in_en guard so this branch is clean standalone.
2026-07-06 22:09:05 -04:00
2ro 062cf2f2ab goblin: always offer a manual Cancel on stuck pendings + soft stale nudge
Some pending transactions showed no way to cancel or clear them, stranding
users with stuck pendings (fee-bug leftovers, and payments orphaned after an
identity switch whose nostr meta lives in another identity's store).

Root causes:
- Goblin receipt: both cancel affordances required the payment's nostr tx_meta,
  which is per-identity. A tx orphaned by an identity switch (or left by an old
  build) has no meta in the active store, so neither Cancel button rendered.
- GRIM tx list + tx detail: the Cancel button was gated behind
  wallet.synced_from_node(), so an unreachable/unsynced node hid it entirely.

Fixes (cancellation is unchanged: the plain libwallet cancel_tx that unlocks the
reserved inputs, GRIM parity, no new money behavior):
- Receipt: universal fallback Cancel for any still-cancellable pending the
  nostr-aware paths miss, running WalletTask::Cancel. ReceiptDetail carries
  can_cancel/stale.
- GRIM list/detail: ungate the local Cancel from node sync (only repost/repeat
  still needs a live height).
- Soft "stale" nudge on pendings older than 24h (WalletTx::stale/STALE_AGE_SECS)
  with the Cancel right there. It only colours a label; nothing auto-cancels on
  a timer (a slow interactive send may still complete).
- New tx_stale / receipt.stale_note strings across all six locales.
- Unit tests for can_cancel state coverage and the stale threshold.
2026-07-06 22:09:05 -04:00
2ro 2790d9a592 goblin: UI pack 153 (portrait lock, home logo, GoblinPay badge, plain QR)
1. Lock Android to portrait (screenOrientation="portrait" on MainActivity).
   Phone-first GRIM-style wallet; no cheap phone/tablet split so the whole
   Android app is locked, matching the single phone surface.
2. Enlarge the mobile Home header wordmark: mark 24 to 36 (+50%), "goblin"
   text 18 to 26, gap 8 to 9, so the top-left lockup carries the same
   visual weight as the 40 to 44px right cluster (owner asked for a
   noticeably bigger bump than the first +25% pass).
3. Swap the Pay-screen inline mark for the official GoblinPay lockup: the
   black Apple-Pay-style badge on light surfaces, the white wordmark on
   dark (theme dark_base split). New img assets from the GoblinPay repo.
4. Plain GoblinPay/receive QR: drop the yellow center-mark overlay from
   w::qr_code (mirrors the prior "plain QR default" decision). Test
   goblin_receive_qr_decodes updated to the plain render.
2026-07-06 22:09:05 -04:00
2ro c6dbec5109 goblin: in-flight request card becomes a single centered spinner
After approve + hold-to-accept, the pending request card no longer keeps
the Decline button and amount alongside a half-width spinner. While the
payment is in flight the whole card is replaced by one centered spinner
labelled with the action, sitting exactly where the card was, and it
vanishes once the send completes and the request clears.
build153
2026-07-06 19:46:59 -04:00
2ro fdbcbf0bdd goblin: audit fixes (money replay guard, honest encrypt prompt) + hardening
P2-1: a money-tier request id is reserved the moment its prompt is raised
(money_pending_ids); a replayed envelope (drain overlapping the live
subscription, or deliberate) returns AlreadyPending / the cached result and
can never raise a second prompt or re-sign. Applies to sign and escalated
encrypt. Regression test covers both replay-while-pending and replay-after-
completion.

P2-3: the money modal for a pay-committing encrypt now shows the inspected
plaintext (escaped + truncated exactly like the sign path), so the user sees
what they are agreeing to pay, not a blind label.

Hardening:
- payment marker set widened (amount, total, price, msat), TODO(audit) kept.
- identity dropped mid-session now answers identity_mismatch on the channel
  (refusal_json per op type) instead of silently timing out the site.
- session-end payload carries reason: revoked (wallet end) / expired (TTL or
  idle sweep, newly published by the tick) / logout (site's own).
- decrypt gets its own soft-cap window (30/min) and an honest 'reading your
  messages' notice, distinct from the signing-volume toast (new locale key
  in all six languages).
- money answers route by the session's site channel key, never the display
  domain.
- committed the auditor's env-gated NIP-44 v2 interop test against
  nostr-tools (GOBLIN_CT1 harness).
2026-07-06 19:46:59 -04:00
2ro 2f52df509e goblin: honour the trust relay hint on the channel + clippy clean
- The session channel now subscribes and publishes on the wallet's relays
  UNION each session's relay hint (channel_relays), dialing any hinted relay
  via the idempotent connect_relays, so wallet and site meet even without a
  shared default relay.
- Clean up clippy findings in the new code (abs_diff, collapsible ifs via
  let-chains, char-array find).
2026-07-06 19:46:59 -04:00
2ro 5da92d0147 goblin: Authorize Sessions GUI (trust grant, money prompt, Trusted Sites) + 6 locales
- Trust-grant modal: password-gated hold-to-confirm 'Trust <domain>?' showing
  identity + truncated npub, the granted low-tier categories (human labels,
  caution lines for unknown/login-excluded kinds), and the fixed money-asks
  line. Folds login in; creates the session on login-POST success.
- Money-tier per-action modal: the v1-authorize-shaped password + hold-to-confirm
  prompt raised over the channel for a sign or a pay-committing encrypt; declines
  on cancel/timeout.
- Trusted Sites settings page: active sessions, what each signs silently, time
  left, pause/resume, and one-tap end (revocation). Row + count in Settings.
- Dispatch + per-frame router for trust/money; PENDING_TRUST wired.
- goblin.trust.*, goblin.money.*, goblin.trusted_sites.*, settings.trusted_sites
  in all six locales; drift test green.
2026-07-06 19:46:59 -04:00
2ro c178f84bf0 goblin: fold cross-worker seam rulings (encrypt/decrypt ops, session-open, 30402 money)
- Add channel encrypt/decrypt ops (identity-key NIP-44) for magick's order-DM
  seal path: EncryptRequest/DecryptRequest, serve_encrypt/serve_decrypt,
  ChannelOp, generalized complete_money. Both low tier + rate-limited; an
  encrypt whose plaintext commits payment escalates to the money prompt;
  decrypt (the DM-read risk) is rate-limited and flagged for the security pass.
- session-open payload aligned to {type,id,identity_pubkey}; sent alongside
  the unchanged 22242 server callback.
- Error codes aligned to the shared set (Refused -> kind_not_in_session).
- Owner ruling: kind 30402 (listing) is money tier, stripped from the silent
  set like 17/22242; spec tier table + category map + tests updated.
2026-07-06 19:46:59 -04:00