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.
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.
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.
Web "Open in Goblin" pay buttons fired generic nostr: URIs, which the
OS routed to whatever social client claims nostr:. Register a dedicated
goblin: scheme and route it (plus nostr: pay URIs) to the prefilled
send-review screen, the same destination as scanning a checkout QR.
Parser (payuri): accept goblin: alongside nostr: (same payload, either
scheme); add is_pay_uri() to tell a payment link from a slatepack.
Runtime: an argv/intent/socket payload that is a pay URI is stashed and
opened by the Goblin surface via SendFlow::from_deeplink, which shares
the exact apply_scan path the camera uses. Desktop cold + warm start
(argv + single-instance socket) and Android cold + warm (intent-filter)
covered.
Registration: Linux .desktop x-scheme-handler/goblin, Android goblin
intent-filter, macOS CFBundleURLTypes, Windows WiX HKCR URL protocol.
versionCode/versionName were inherited from GRIM (5 / "0.3.6") at the fork and
never rewired to Goblin's build number, so every build shipped as versionCode 5
- Android could not tell builds apart and Settings showed 0.3.6 for build 130.
Gradle now derives both from the GOBLIN_BUILD env (the same number the Rust side
stamps into crate::BUILD), with a fallback to the old values for a keyless local
gradle build.
Fire a separate high-importance system notification when an incoming
payment request (Invoice1 -> SurfaceRequest) is ingested over nostr,
mirroring the existing received-payment notification (id=2). Fail-open on
a missing JNI handle; fires once per not-yet-seen slate. No-op off Android.
Also add examples/tunnel_measure.rs, a dev harness for measuring the Nym
read tunnel (cold connect + warm per-fetch latency over the real transport).
The Android keep-alive notification's background job is the light Nostr-over-Nym
payment listen ("Listening for payments"); the heavy integrated node is no
longer STARTable from it (Goblin defaults to an external node). Only STOP is kept
as a safety valve. Also re-enable the macOS release job (release: published) so a
published release attaches a universal .app build on the native runner.
Money path:
- Scoped, unbonded Nym exit for the money-path relay: the wallet dials a
relay operator's co-located exit over a MixnetStream (src/nym/streamexit.rs)
which pipes to its one relay; hostname-validated TLS end to end, no public
DNS. Anchor + fallback (never pin-only): any exit failure degrades to the
smolmix tunnel. relay.goblin.st's exit address is pinned in the relay pool
(src/nostr/pool.rs) and the maintainer gist so it bootstraps offline.
- STREAM_SETTLE bridges the open-before-accept gap so the first TLS byte is
not dropped into a stalled handshake.
- Verified end to end: two wallets complete a real gift-wrapped Grin payment
through relay.goblin.st over the exit, finalized + posted on mainnet
(src/wallet/e2e.rs, ignored live test).
Encryption:
- Adopt NIP-44 v3 for the NIP-17 gift-wrap path (G4): src/nostr/wrapv3.rs,
nip44 path dep; v3<->v3 and v3->v2 interop.
Also: mix-DNS (src/nym/dns.rs), full localization pass, GUI polish,
avatar-ring example, Android icon/script updates, GRIM deviation notes,
xrelay + connect-timing tests.
All app icons now derive from two sources, end to end:
img/goblin-icon.png gradient app icon (yellow gradient + black mascot)
img/goblin-mark-black.svg black mascot mark (vector) — Android adaptive fg
- scripts/gen_icons.sh rewritten: one run regenerates the desktop/egui window
icon (img/icon.png), the Linux AppImage AppDir icon, all Android launcher +
adaptive-foreground mipmaps, the WiX installer icon, and the macOS .icns.
Dropped the dead goblin-mask*.png pipeline (the adaptive foreground is now the
SVG mark composited by the OS over #FFD60A), so the script no longer references
files that were removed.
- scripts/make-icns.py: new, dependency-free multi-resolution .icns builder
(iconutil/png2icns aren't always present; ImageMagick alone emits one size).
- wix/Product.ico REPLACED with the Goblin logo (was a stale icon).
- Regenerated macOS AppIcon.icns, Linux goblin.png, all Android mipmaps,
img/icon.png. Tracked the goblin-mark-{black,white}.svg sources.
- Configurable name authority (Settings → Identity → Name authority): bare
names resolve there, own-domain names show bare, foreign verified names show
'name · domain' with a check — no '@' anywhere. Lets bob@otherdomain pay
alice@goblin.st. Home domain derived from the configured server.
- Note entry is now a modal that floats above the soft keyboard (dimmed
backdrop) instead of an inline editor the keyboard covered.
- Backup export SAVES to a chosen location (Android CREATE_DOCUMENT / desktop
save dialog) instead of opening the share sheet.
- Onboarding status-bar icons are legible again (white on the dark surface,
not black); identity step is less wordy and drops the '@' prefix; claiming a
name during onboarding now republishes kind 0 so it's visible immediately.
- App-open name re-verify sweep (persisted, runs if >78s since last).
- Advanced 'Manage node connection' opens GRIM's native Connections UI.
- Manual slatepack paste: removed the QR icon. Pay screen: bolder, bigger ツ.
- Localized new strings across 6 locales.
Cash App-style scan screen: a segmented Scan | My Code control (new w::segmented)
over either the camera or your own payment QR. My Code shows the @handle above a
big nprofile QR with the Goblin mark nested in a YELLOW center (was white — same
19% footprint, yellow reads as light to a scanner so the High-ECC recovery is
unchanged), and a native Share button. Added share_text to PlatformCallbacks
(Android ACTION_SEND text/plain via a new shareText JNI method; desktop falls
back to clipboard) to share the npub + nprofile link.
Guard against paying more than the spendable balance at every entry: the Pay
tab, the send-flow amount step, and the Review screen all render the amount in
red with "You don't have enough grin" and refuse to proceed, so a payment that
would only fail later at the node is stopped up front. On Android the phone
gives a short error buzz on the blocked tap — new VIBRATE bridge
(PlatformCallbacks::vibrate_error -> MainActivity.vibrateError); no-op on
desktop. Grim silently clamped the amount and showed nothing.
Also enlarge and vertically center the magnifier in the "Send to" search field
(an 18px muted label became a 22px centered glyph).
Build 37 wrongly forced Grim's on-screen virtual keyboard on Android (.soft_keyboard())
after the emulator's broken IME misled me into thinking the native keyboard was dead.
That brought up a virtual keyboard AND double-typed. Reverted:
- Removed .soft_keyboard() from every field; they now use Grim's TextEdit defaults
(no_soft_keyboard = is_android()) → the user's native keyboard on Android, exactly
like Grim, no virtual keyboard, single input.
- Reverted edit.rs IMEAllowed/on_soft_input to Grim's exact behavior; kept only the
additive display builders (hint_text/text_color/body) the Goblin design needs.
- Manifest: removed windowSoftInputMode and the keyboard/keyboardHidden/navigation
configChanges flags I'd added (Grim has none) so input matches Grim; kept the
uiMode/density flags + isFinishing() guard for the dark-mode crash fix.
The app's input path now diffs from Grim only by the additive display builders.
(The Android emulator's IME is aborted regardless — a known emulator quirk — so the
native keyboard is validated on a real device, where Grim works.)
- Typing (critical): the Goblin UI used plain egui::TextEdit, which never
received input because Grim's native-keyboard path only feeds Grim's own
TextEdit widget. The native IME is also aborted by the windowing system on
this app. Fix: every Goblin field now uses Grim's TextEdit, opted into
Grim's own on-screen keyboard on Android via a new .soft_keyboard() builder
(no native-IME dependency). Additive TextEdit options (hint_text/text_color/
body/soft_keyboard) leave Grim's defaults and existing call sites unchanged.
- Install alongside Grim: FileProvider authority mw.gri.android.fileprovider →
st.goblin.wallet.fileprovider (Android rejects duplicate authorities).
- Status bar: app draws edge-to-edge, so dark icons vanished on the dark theme.
New theme-aware JNI call sets light/dark status-bar icons per theme.
- Edge padding: centered_column now keeps an 18dp side gutter on phones instead
of running content flush to the screen edge.
Validated on the API-36 emulator: typed into fields, padding gutter present.
Infrastructure (P0): deployed nostr-rs-relay (wss://nrelay.us-ea.st) and the
goblin-nip05d NIP-05 service (goblin.st) on us-ea.st with TLS + DNS.
Brand & theme (P1): Goblin name/icon/data-dir (.goblin); three-theme token
system (light/dark/yellow) in gui/theme.rs with colors.rs remapped as a shim;
Geist + Geist Mono fonts; AppConfig theme/density/last_wallet_id.
Nostr subsystem (P2-P3): src/nostr/ with NIP-06 identity (seed-derived,
NIP-49 encrypted), per-wallet rkv archive, guarded ingest policy (never
auto-pays Invoice1; binds replies to the stored counterparty npub), NIP-17
send/receive pipeline, NIP-05 client. Relay traffic routed over the embedded
arti Tor client via a custom WebSocketTransport. Wired into Wallet lifecycle
and the task handler. 26 unit tests pass.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>