1
0
forked from GRIN/grim

994 Commits

Author SHA1 Message Date
2ro 5445b48a69 Build 69: fetch nym from our own mirror, not upstream GitHub
Point .github/actions/fetch-nym at git.us-ea.st/GRIN/nym (branch goblin =
upstream nymtech/nym @ b6eb391 + our Android webpki-roots patch) and clone it
directly. Drops both the upstream-GitHub fetch and the git-apply patch step —
the last third-party dependency in the build chain is now self-hosted. Removes
the now-unused ci/nym-webpki-android.patch.
build69
2026-06-14 12:32:56 -04:00
2ro 56099539aa Build 68: build the GRIM way — integrate the code.gri.mw/DEV toolchains
Add scripts/toolchain.sh: fetches GRIM's canonical build toolchains (custom
NDK r29, zig, appimagetool + type2 runtime, and optionally the Android SDK /
gradle / osxcross) into a gitignored .toolchains/ and writes env.sh.

linux/build_release.sh and scripts/android.sh now source .toolchains/env.sh,
preferring the DEV toolchains and falling back to system installs:
- Android links against the custom NDK r29 (rebuilt LLVM), producing
  16 KB page-aligned .so libraries — required by the Play Store.
- The Linux AppImage cross-builds with the DEV zig + appimagetool; bindgen is
  pointed at the host kernel headers so v4l2-sys finds linux/videodev2.h under
  zig's glibc-2.17 sysroot.

Also fix the stale .gitignore AppRun entry (Grim.AppDir -> Goblin.AppDir).
2026-06-14 11:55:09 -04:00
2ro bb3b8c4ecc Build 67: Pay over-balance shake, manual slatepacks, green CI, slimmer builds
Pay: stop reddening the amount while typing — requesting more than you hold is
a valid request, so the digits stay black. Pressing Pay without enough funds
now shakes and briefly flashes the amount red and buzzes the phone, then
settles back. Bigger scan-to-pay puck.

Settings: the username "@" moves inside the field as the "@yourname"
placeholder (a leading "@" the user types is stripped). Mixnet routing is
shortened to "All traffic" and flagged in the privacy color. The Build row
links to GitHub releases. Network reads "MW + Nym mixnet + nostr". The Nym
third-party row shows the linked SDK version instead of "socks5".

Wallet: expose GRIM's native by-hand slatepack flow as an advanced
"Slatepacks" page — paste to receive/pay/finalize, or create a payment
slatepack to hand over. The fallback for when a payment can't ride a @username.

CI: fix the red GitHub builds. nym-sdk is a path dep on ../nym, which the
runners didn't have. A composite action materializes the pinned upstream nym
commit plus Goblin's small Android webpki patch before each build; aws-lc-sys
uses prebuilt NASM on native Windows.

Builds: strip release binaries, dropping ~17 MB of debug symbols from the
desktop build.
2026-06-14 06:25:44 -04:00
2ro 2578a35cf7 Build 66: disable clearnet update-check; drop sidecar build steps
Security (audit H-2): the legacy update check is OFF by default. It hit
code.gri.mw (GRIM's gitea) directly over CLEARNET via the old HttpClient —
leaking "this user runs Goblin" metadata on every wallet-list view, which
defeats the nothing-clearnet mixnet model, and it pointed at the wrong
project's releases anyway. Opt-in only until reworked to run over the
mixnet against Goblin's own releases.

Build: with the Nym SDK linked in-process there's no sidecar binary to
embed or bundle. linux/build_release.sh drops the GOBLIN_NYM_UNIX_BIN
embed (AppImage is one self-contained binary); scripts/android.sh stops
bundling nym-socks5-client into jniLibs (the cdylib links nym-sdk
directly); scripts/nym-android.sh deleted.
2026-06-14 04:07:39 -04:00
2ro 63d5ca2b5f Build 65: link the Nym SDK in-process — no sidecar subprocess
Goblin now links nym-sdk directly and runs its SOCKS5 client on an
internal tokio runtime exposing 127.0.0.1:1080 — the same loopback seam
the transport already dials. There is no sidecar subprocess and no
bundled/embedded/sideloaded helper binary; the goblin process itself owns
:1080. This mirrors how GRIM links arti/Tor in-process. Verified live: the
mixnet comes up in ~1.4-2s (gateway persisted in ~/.goblin/nym, reused
across launches) and a relay connects in ~2s over it, with no separate
process.

- Cargo.toml: add nym-sdk (path dep on the local nym checkout, which carries
  the Android webpki-roots patch) + rustls with the ring feature.
- src/lib.rs: install rustls' ring CryptoProvider at startup. Linking nym-sdk
  pulls aws-lc-rs alongside our ring; with two providers present rustls 0.23
  won't auto-pick a default and tokio-tungstenite/reqwest panic on the first
  TLS handshake. nym uses its own explicit provider, so this only steers our
  relay/HTTP TLS.
- src/nym/sidecar.rs: replace the subprocess machinery with an in-process
  Socks5MixnetClient (persistent storage; ephemeral fallback) kept alive for
  the process lifetime on a dedicated runtime. Drops the binary lookup, embed
  extraction, init/launch, and child management.
- build.rs: drop the GOBLIN_NYM_*_BIN embed block (nothing to embed).
- src/nym/{mod,transport}.rs, src/nostr/{mod,avatar}.rs: docs now describe the
  in-process client; clear stray "old Tor/arti" wording (no Tor transport code
  remains — only grin-core's slatepack OnionV3Address, which is unrelated).

Also: named users now get the pubkey-seeded gradient background with their
initial composited on top (instead of the Grin mark) — gui identicon.rs gains
gradient_bg_svg and widgets.rs gains gradient_letter_avatar; avatar_any routes
named keys to it. Verified live with @nymgoblin.
2026-06-14 03:46:02 -04:00
2ro 8c48d2f5ce Build 64: embed the Nym sidecar into the Linux binary (single-file AppImage)
The Linux release no longer ships a loose nym-socks5-client beside AppRun.
It's baked into the goblin binary the same way the Windows build bakes it
into goblin.exe, and extracted to ~/.local/share/Goblin at first launch
(chmod +x on Unix). The AppImage is now one self-contained file with nothing
loose to misplace.

- build.rs: generalised the Windows-only GOBLIN_NYM_WIN_BIN embed to a
  cross-platform path. GOBLIN_NYM_UNIX_BIN embeds the Linux/macOS sidecar;
  Android never embeds (its sidecar rides in the APK's jniLibs).
- src/nym/sidecar.rs: the embedded const, extract_embedded_sidecar, and the
  binary_path extract branch now cover all non-Android targets, with a Unix
  chmod +x on the freshly written file.
- linux/build_release.sh: rewritten to set GOBLIN_NYM_UNIX_BIN, apply the
  glibc-2.17 zigbuild + CRoaring-AVX512/vendored-OpenSSL fixes, and assemble
  an AppDir with no loose sidecar.
2026-06-14 02:23:10 -04:00
2ro bfed0a1cb9 Build 63: connect over Nym in seconds, yellow Pay page, gradient-avatar fixes
Nym "Connecting..." for ~1 minute — root cause fixed. open() spawned the
wallet sync thread BEFORE init_nostr() created the nostr service, so the
sync loop's first pass at service.start() found no service and skipped
it; the service only started on the next sync cycle, a full SYNC_DELAY
(60s) later. Measured on the dev box: the gap between "identity ready"
and "starting service" was 62s, while the relay itself connects over the
mixnet in ~2s. Initialising nostr before start_sync collapses that gap
to ~1ms (verified 62s -> 1ms). The mixnet sidecar already warms up at app
launch, so the relay connect was the only thing waiting — and it waited
on loop timing, not on Nym. Added a "first relay Connected ~Nms" log line
so the timing is observable from logs going forward.

Pay tab is now painted in the yellow theme (Cash App-style brand surface)
regardless of the active theme, via a scoped theme override held across
the central panel so the fill and every widget pick up the yellow tokens
together.

Gradient avatar now renders for anonymous npubs on the onboarding
identity card and the mobile home header — both hardcoded a flat "N"
letter tile while Settings already showed the gradient. The header falls
back to the short npub so avatar_any takes the gradient branch.
2026-06-14 01:58:19 -04:00
2ro 63bf92f172 Build 62: fix the in-field QR scan icon not opening the scanner
Build 60 moved the scan-to-pay screen behind a new `scan_open` flag (for the
Scan | My Code toggle), but the recipient picker (Send to / Request from) still
opened the scanner by setting only `self.scan` — which no longer gates the
screen. So tapping the QR icon in the search field silently started the camera
and showed nothing. Set `scan_open` + the Scan tab there too (keeping `scan` so
the Scan branch does not double-start the camera).
2026-06-14 00:58:01 -04:00
2ro fc348c1843 Build 61: deterministic gradient avatars for anonymous npubs
Anonymous users (no @handle, no kind-0 picture) had a flat colored tile with an
N - meaningless, since their identity IS the key. Replace it with a pubkey-seeded
two-tone gradient + the Grin mark (avatar = f(pubkey)): same key -> identical
avatar on every surface, nothing to upload/store/sync. Ported the shared
reference (identicon.rs, f64 math, SHA-256 of the lowercase hex seed) and rendered
via egui SVG loader (cached per pubkey). avatar_any/activity_row now take the
npub/hex and use the gradient when the display name is an npub, the lettered tile
otherwise.
2026-06-13 23:56:31 -04:00
2ro ea70923e83 Build 60: Scan-to-pay gets a Scan | My Code toggle, yellow QR, native share
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.
2026-06-13 23:15:51 -04:00
2ro 3ebae8807c Build 59: start the nostr service immediately on wallet open (fix slow connect)
The Nym/relay connection could take up to a full SYNC_DELAY (60s) to even start:
the nostr service was kicked off deep inside the wallet sync loop, behind the
grin node-sync checks and !sync_error, so opening a wallet left the profile on
"Connecting..." until the next 60s sync tick (or never, while the node errored).
Move the (idempotent) service start to the very top of the loop, right after the
open check - independent of node sync - so the connection comes up right away.
2026-06-13 23:15:36 -04:00
2ro ca038c6e14 Build 58: heavier Pay puck — Noto Sans JP Black weight ツ
Owner wanted more "black strength" on the puck mark; re-subset the same one
glyph from Noto Sans CJK JP Black (was Regular) for thicker, more confident
strokes. Same noto-tsu family / path, no code change.
2026-06-13 21:49:07 -04:00
2ro 6ce70aee7e Build 57: Pay puck uses a Noto Sans JP ツ instead of Gamja Flower
Owner disliked the Gamja Flower ツ style on the center Pay puck. Replace it with
Noto Sans JP's cleaner, more geometric katakana tsu — subset to ONLY that one
glyph (~1.7 KB) the same way the old one was, loaded as the "noto-tsu" family and
drawn at the puck (mod.rs). Drop the now-unused Gamja Flower font + its license.
2026-06-13 21:45:56 -04:00
2ro de7007269f Build 56: wait for the mixnet before dialing relays; no on-screen keyboard
Two fixes from live testing:

1) Stuck on "Connecting…": the nostr service dialed relays without waiting for
   the bundled Nym sidecar to be up. On a cold start a fast wallet-open beat the
   mixnet bootstrap, every relay failed, and nostr-sdk backed off — so the wallet
   sat on "Connecting…" long after the mixnet was ready. Now we wait for the
   sidecar SOCKS5 port before connecting (instant once it is warm). Verified:
   service start -> relay catch-up in ~5s; the flag (Build 55) reflects it within
   2s.

2) On-screen keyboard on desktop: upstream Grim pops its own virtual keyboard on
   desktop (no_soft_keyboard = is_android()), which looked wrong in the wallet
   flows and competed with physical typing (intermittent dropped/duplicated
   chars). Goblin now uses native input on every platform — Android IME via JNI,
   physical keyboard on desktop — by defaulting no_soft_keyboard to true. Verified
   on desktop: no on-screen keyboard, reliable typing.
2026-06-13 21:39:17 -04:00
2ro 6dff408766 Build 55: surface the Nym/relay connection promptly (fix stuck "Connecting…")
The nostr service only refreshed its connected flag inside the select!s
sleep(30s) branch, which restarted on every incoming notification — so the
flag could lag the real relay state by 30s+ (or, under steady event flow,
never update), leaving the UI stuck on "Connecting…" even though a relay
handshake over the Nym mixnet completes in ~2s (measured). Set the flag right
after the startup connect, and poll it on an independent 2s interval; the 30s
heartbeat work (persist last-seen, TTL prune) stays on its own cadence.
2026-06-13 21:23:09 -04:00
2ro 6621dc6aaa Build 54: Windows single-file + silent, logged sidecar
Address Windows feedback (visible console window; no diagnostics when the
mixnet stalled):
- Hide the sidecar console: spawn nym-socks5-client.exe with CREATE_NO_WINDOW
  so launching it no longer flashes a terminal.
- All-in-one: embed the Windows sidecar into goblin.exe (build.rs, gated on
  GOBLIN_NYM_WIN_BIN) and extract it to %LOCALAPPDATA%\Goblin at first run, so
  the release is a single self-contained .exe with no loose helper to misplace.
- Log the sidecar to ~/.goblin/nym-sidecar.log (all platforms) instead of a
  null sink, so a stalled bootstrap is diagnosable.

Verified under wine: goblin.exe extracts the embedded sidecar, launches it,
and it opens the SOCKS5 proxy on 127.0.0.1:1080.
2026-06-13 20:30:33 -04:00
2ro 329067e1c2 Build 53: Windows + Android support with a per-platform Nym sidecar
Ship the bundled nym-socks5-client on Windows and Android, not just Linux:
- sidecar.rs resolves the binary per platform — nym-socks5-client.exe on
  Windows; on Android the sidecar rides in the APK jniLibs as
  libnym_socks5_client.so and is launched from the native-library dir (the
  one exec-allowed path), located via NATIVE_LIBS_DIR.
- Restore a vendored, statically-linked OpenSSL. Upstream Grim got this from
  arti's static feature; dropping arti for Nym took it with it, which broke
  Android/cross builds (no system OpenSSL for the target) and left desktop
  dynamically linked to libssl. Inert on Windows/macOS (SChannel/Security.fw).
- android.sh bundles the sidecar into jniLibs per ABI; scripts/nym-android.sh
  cross-builds it. Onboarding copy: Tor -> the Nym mixnet.

Verified end to end on an x86_64 emulator: the sidecar extracts, launches,
initialises, and opens the mixnet SOCKS5 proxy on 127.0.0.1:1080.
2026-06-13 19:57:36 -04:00
2ro 0f46145f46 Build 52: drop the Pay keypad lower; NIP-44 + Mimblewimble-plus-Nym labels
- Pay tab keypad sits in the lower third (thumb reach) instead of floating in
  the middle with a big empty gap below it (pay_ui computes a drop spacer on
  phone layouts).
- Review/Confirm "Delivery" reads "NIP-44 encrypted, over Nym" (naming the
  actual gift-wrap encryption), and "Privacy" reads "Mimblewimble + Nym" on
  the review and the receipt — surfacing both the chain and network layers.

Verified live at 394px across the Pay tab, review, confirm and receipt.
2026-06-13 18:54:11 -04:00
2ro ba5ddf07ac Build 51: fit the balance, center the send flow, fix the success logo
On-device review surfaced UI overflow/centering issues at phone width:

- Balance hero shrinks to fit: amount_text_centered_ink measures the text and
  scales the font down when it would run off the edge (e.g. 0.47520721ツ).
- Send flow sits closer to the screen edges (smaller side gutters) and is
  properly centered: the Review/Confirm hero now uses a centered, wrapping
  layout instead of a fragile manual offset that clipped a long npub and
  threw off the centering. info_row values truncate instead of overflowing.
- Recipient picker: the suggested row shows the full npub in the grey subtitle
  (truncated to fit) instead of repeating the same shortened npub as the title.
- Success screen uses the goblin head (goblin-logo2), not the mascot mask.

Verified live at 394px across balance, recipient, review, confirm and the
success screen.
2026-06-13 15:55:02 -04:00
2ro c05074faac Build 50: production Nym requester; correct sender amount/fee; Nym copy
- Bake the always-on us-ea.st network requester into NETWORK_REQUESTER so a
  shipped wallet routes through the mixnet out of the box (override with
  GOBLIN_NYM_PROVIDER). Verified e2e: real wallet-to-wallet payment across two
  mainnet nodes (grincoin.org → main.gri.mw), recipient received the exact
  amount, all traffic over Nym.
- Fix the sender's displayed amount: a sent tx debits inputs and credits change,
  so debited-minus-credited is amount PLUS fee. Subtract the fee so the activity
  feed, receipt and history show the value that reached the recipient (0.01),
  not the fee-inclusive net (0.033). The receipt's "Network fee" now reads the
  real kernel fee instead of the change.
- Replace the remaining user-facing "Tor" copy with Nym (send/request review
  delivery line, connection status chips, onboarding, the Privacy settings row).

38 lib tests pass.
2026-06-13 15:15:41 -04:00
2ro c4d26d3b7f Build 49: auto-expire stale transactions; de-dupe receipt counterparty
Pending transactions that never complete now auto-cancel/expire after a
configurable window (NostrConfig::expiry_secs, default 24h; lower it in
nostr.toml to test). A sync-loop sweep (NostrService::expire_stale):

- cancels stale outgoing sends + invoices we paid via GRIM's cancel_tx
  (WalletTask::Cancel), releasing the outputs they had locked;
- annotates incoming payments / invoices we issued as Cancelled only
  (a late on-chain confirmation still wins);
- marks pending incoming requests Expired.

The activity feed and receipt now render "canceled" (this also fixes a
latent bug where a manually-cancelled tx still showed "pending").

Receipt de-duplication: the To/From name rows are shown only when the
counterparty has a real identity (petname or verified NIP-05); a bare
npub now appears once, in the "nostr" row, instead of twice.

38 lib tests pass (2 new for the expiry bucket logic).
2026-06-13 14:31:14 -04:00
2ro 695c3e6d4f Build 48: replace Tor with the Nym mixnet; remove arti
Route every relay and HTTP request (nostr relays, NIP-05, price) through
a local nym-socks5-client sidecar on 127.0.0.1:1080, so all traffic
egresses via the 5-hop Nym mixnet and nothing touches the clear net.

- Add src/nym/: SOCKS5 HTTP client (reqwest socks5h), NymWebSocketTransport
  for the nostr relay pool (tokio-socks dial + TLS/ws handshake over the
  mixnet), and a sidecar launcher that reuses or spawns nym-socks5-client.
- Swap the nostr-sdk transport off ArtiWebSocketTransport; route nip05.rs
  and price.rs off Tor; revert the clearnet username-lookup shortcut.
- Remove the embedded arti Tor client wholesale: the onion-service
  listener and send-to-onion path in the wallet, the legacy transport
  GUI tab, the Tor settings page, src/tor/, the webtunnel pluggable
  transport (Go build + submodule), and all arti crates from Cargo.toml.

The Grin node connection is unchanged (chain data, no payment metadata,
and never used Tor). The network requester the sidecar routes through is
configured via GOBLIN_NYM_PROVIDER / NETWORK_REQUESTER at deploy time.
2026-06-13 13:29:21 -04:00
2ro dda07dee0a Build 47: username lookup over clearnet, off Tor
The read-only NIP-05 username lookups (resolve + check_availability) carry no
secret and need no anonymity — routing them over Tor is what made choosing a
username slow. They now use a fast clearnet HTTPS GET (reqwest + rustls/ring,
so it still cross-compiles to Android), falling back to Tor only if clearnet is
blocked. The anonymity-critical paths (slatepack delivery, NIP-98-signed ops)
are untouched.
2026-06-13 12:40:36 -04:00
2ro 1ac1186319 Docs: link NIPs/kinds on first mention; gitignore screenshots/
Every first mention of a NIP or nostr kind in the README now links to
nips.nostr.com (NIPs) or nostrbook.dev (kinds). Add screenshots/ to .gitignore
(local gallery captures for the site).
2026-06-13 06:35:55 -04:00
2ro 4cad00079e Build 45: receipts, activity grouping, profiles + block
Rich transaction receipt screen (tap an activity row, or the new Receipt button
on a send's success screen): counterparty, time, note, amount, and a
Transaction-details card joining GRIM's local metadata with the nostr
npub/username — status (Complete vs Pending N/min-conf), To/From, network fee,
Mimblewimble, and the slate id. A local archive, like GRIM.

Group the Activity feed into Pending (unconfirmed) + per-day sections.

Contact profile screen (tap a peer or the receipt counterparty): who they are,
the history between you, a Pay shortcut, and Block — a nostr-level mute that
drops their incoming payments/requests (Contact.blocked, checked in ingest).

Refine success/denied copy ("to/from" by direction; "ask them to send you grin
instead"). Make the profile avatar display-only — no custom-picture upload.
2026-06-13 06:29:57 -04:00
2ro 143f4230c9 Build 44: fix recurring macOS release build
release.yml's `files:` upload globs still used ${{ inputs.tag }}, which is empty
on a `release: published` event, so the auto-built macOS universal zip never
matched its filename and didn't attach — the job went green while uploading
nothing. Use the inputs.tag || release.tag_name fallback for the globs too.

Also fix the CI build number: building from the public single-commit squash
makes `git rev-list b51a46b..HEAD` fail (the fork base isn't an ancestor), so
build.rs fell back to "Build dev". build.rs now honours a GOBLIN_BUILD env
override and release.yml passes GOBLIN_BUILD=${TAG#build}, so CI artifacts carry
the real build number.
2026-06-13 05:44:47 -04:00
2ro f714e4498a Build 43: real payment requests + incoming-requests opt-out
Wire GRIM's receiver-initiated invoice (Invoice1) over nostr so the Pay-tab
"Request" button actually asks someone for money: pick a contact, issue an
Invoice1, DM it; they get the existing approve-to-pay card. New
WalletTask::NostrRequest mirrors NostrSend (issue_invoice + RequestedByUs /
AwaitingI2, reusing send_payment_dm); the send flow gains a request mode
(Request from -> Confirm request -> Send request -> Requested) with no balance
guard, since requesting isn't spending.

Incoming requests are now opt-out (Settings -> Requests, on by default): when
off, an incoming Invoice1 is dropped and the preference is advertised in the
kind-0 profile (goblin_accepts_requests) so a requester sees "Could not
request" before sending. Adds a Cash App-style toggle switch widget.

Also enlarge the center Pay puck in the floating nav, and make the macOS
release build recur: release.yml now triggers on release publish (macOS only,
since Linux/Windows/Android/AppImage are built locally).
2026-06-13 05:08:51 -04:00
2ro 2e8829ef83 Build 42: block over-balance sends + roomier search icon
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).
2026-06-13 02:51:29 -04:00
2ro 9d74d6fac5 Build 40: roomier Pay keypad spacing (Cash App-style)
The amount keypad rows were bunched tight (56px keys, 4px gaps) high on the
Pay screen, leaving a large empty area at the bottom. Now: taller (58) and
wider (pad 332, 14px column gaps) keys, bigger digits (30), and adaptive
row spacing that spreads the four rows toward the bottom when there's room
(the Pay tab) while staying compact where there isn't (the send flow).
Clamped so it never overflows or stretches absurdly.
2026-06-13 01:06:34 -04:00
2ro 4d5db923ea docs: de-duplicate the nostr_password comment in Wallet::open
Audit cleanup — collapse the garbled double comment to one line. No code change.
2026-06-12 23:10:44 -04:00
2ro f210180de6 Build 39: match Grim's focus handling on text fields (.focus(false))
Grim never lets a field default to constant focus. Its TextEdit defaults
to focus = true, which means an unfocused field calls request_focus()
EVERY frame. Grim's own usage always overrides this — every field is
.focus(false) (focus only on tap), and at most one gets .focus(first_draw)
to grab focus once when a screen opens (see grim wallets/modals/add.rs).

Goblin's converted fields skipped this, so all ~16 fields requested focus
each frame and fought each other on multi-field screens (wallet setup,
import, rotate). Set every field to .focus(false) to match Grim exactly:
fields now focus on tap and bring up the native keyboard, with no thrash.

The native IME path itself is already identical to Grim (verified: edit.rs
differs only by additive display builders; MainActivity onTextInput JNI
plumbing is byte-for-byte Grim; android/winit/eframe/egui/jni deps match).
2026-06-12 22:31:54 -04:00
2ro 1b5352da0d Build 38: use the native Android keyboard like Grim (revert the virtual-keyboard mistake)
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.)
2026-06-12 22:14:49 -04:00
2ro 2cc023b905 Build 37: fix Android — typing, install-alongside-Grim, status bar, edge padding
- 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.
2026-06-12 21:47:54 -04:00
2ro 993a438e18 Build 36: friendlier ツ on the Pay puck via a Gamja Flower subset
The center Pay puck rendered ツ through the Noto SC fallback (Geist has no
katakana) — a stiff, geometric glyph. Embed a 1.2 KB subset of Gamja Flower
(OFL) containing only ツ and use it solely at that one widget; its ツ is the
cute winking-smiley shape. Every other ツ (balances, title, activity) is
untouched.
2026-06-12 18:15:16 -04:00
2ro b1c3c07dac Build 35: lead Tor bootstrap with the maintained bridge solo
The default webtunnel list bundles wt.gri.mw with public bridges that rot;
bundling them means arti can fixate on a dead/zombie bridge (front up, tunnel
dead) and burn the whole 120s bootstrap timeout — observed live on Android as
'stuck at 15%, had to reset bootstrapping too many times' (48 attempts on a
zombie, 1 on the good bridge). Now the first attempt uses the maintained
default alone; the public bridges stay as fallback in pairs only if it's
unreachable. Verified on the API-36 emulator: Tor reaches 100% over wt.gri.mw,
zero attempts on the dead bridge.
2026-06-12 17:44:04 -04:00
2ro 878f7728eb Build 34: harden the nostr ingest path (audit Medium items)
- N-F1: commit the wrap/rumor/slate dedup markers immediately after the
  durable receive/finalize, before the reply+sync tail, so a crash there
  can't re-trigger the action on catch-up. (grin + decide() already
  backstopped it; this closes the window cleanly.)
- N-F3: prune the processed-dedup store hourly in the heartbeat, not only
  at startup — a long-lived session could otherwise grow it unbounded
  under fresh-keypair spam since the 30-day TTL never re-applied.
- N-F2: kept Created/SendFailed in the finalize allow-set (removing them
  would strand a real send whose S1 reached the peer before we persisted
  AwaitingS2) and documented why it is not a forgery vector; added a test.
- Update replay_check e2e: a same-pubkey second register is now blocked by
  the name-change cooldown (fires before the one-name rule); accept either.

Validated: 35 lib tests + live nip17_slatepack_roundtrip + replay_check green.
2026-06-12 12:48:34 -04:00
2ro 60e4e8b5a9 Build 33: security audit fixes — remove the clearnet bridge probe, cap untrusted responses
- SECURITY (High): drop sort_bridges_by_reachability / bridge_probe_addr. The
  Build 30 probe did clearnet DNS + direct TCP to bridge endpoints outside Tor
  on every startup, deanonymizing bridge users. The consensus-cache keep + 120s
  timeout + pre-warm remain and are the real fix for slow first connect.
- SECURITY (Low): cap HTTP response bodies from the untrusted goblin.st server
  at 2 MiB, streamed so a lying/absent Content-Length can't OOM the wallet.
2026-06-12 12:18:57 -04:00
2ro b9ce88e996 Build 32: federate identity + sharpen identity chrome
- Accept any NIP-05 domain in the send flow (user@domain resolves and pays;
  foreign handles display with their domain and hit the unverified-key gate)
- Share and scan nprofile (npub + relay hints) so a recipient is reachable
  with no registry/indexer lookup; hints ride the DM send path
- Receive QR, copy buttons and settings row now emit nostr ID (nprofile)
- Sidebar identity chip truncates long handles on one line (was wrapping)
- Crisper small goblin mark via a 2x raster at chip sizes
- Map the server's name-change cooldown to friendly copy in claim/release
2026-06-12 11:54:35 -04:00
2ro 60414e9477 Build 31: survive Android configuration changes — the activity-recreate path killed the whole process (crash on open with forced dark mode); widen configChanges, gate process teardown on isFinishing 2026-06-12 11:02:54 -04:00
2ro 7eefb54075 Build 30: Tor up in seconds — probe bridges, keep consensus cache, pre-warm at start; borderless window frame, vector sidebar mark, Wayland app id, v2 icon set 2026-06-12 02:51:00 -04:00
2ro cda1be992f Build 29: standardize the AI credit line in the README 2026-06-12 02:00:01 -04:00
2ro 9257f82dcf Build 28: drop internal dev docs and upstream Forgejo CI from the repo 2026-06-12 01:47:37 -04:00
2ro 7f09598298 Build 27: let the Android app build without upstream's private maven mirror 2026-06-12 01:23:29 -04:00
2ro 03741f7839 Build 26: replace upstream release CI with native-runner workflow for GitHub 2026-06-12 01:23:29 -04:00
Claude 78f629d8d3 Build 25: default to a steadier instant node + add the Goblin node
main.gri.mw has intermittent issues, so the default external ("instant") node
is now grincoin.org — both the onboarding first-connect and the head of the
external-node list. Adds the Goblin-run node https://main.us-ea.st as a default
option. (Relays already default to ours, nrelay.us-ea.st, plus relay.damus.io
and nos.lol — no change needed.)
2026-06-11 23:48:31 -04:00
Claude 68d72fa853 Build 24: equal-size buttons in the release-username confirm gate
"Keep it" used big_action_on_card (56px / 17px) while "Release it" used
big_action_on_card_ink (44px / 15px), so the two confirm buttons were
visibly different sizes. Both now render with big_action_on_card_ink at the
intended 44px (matching the layout's scope), differing only in text color.
2026-06-11 23:41:38 -04:00
Claude 95403516d5 Build 23: README rewrite + banner — describe Goblin, not Grim
Replace the inherited Grim README with one that reflects what Goblin actually
is: a private, Cash App-style GRIN wallet that pays @usernames via NIP-17
gift-wrapped DMs over Tor, with in-app nostr identity and the goblin.st
identity service. Adds the Goblin banner, a payment-flow diagram, correct
build instructions (binary is `goblin`), and keeps the Claude credit.
2026-06-11 23:26:11 -04:00
Claude f2402eb24d Build 22: security hardening follow-ups
From the audit's deferred P2 list:
- Global gift-wrap decrypt ceiling (~120/min across all senders) so
  fresh-keypair spam can't force unbounded NIP-44 decrypts. The per-sender
  limit only applied after the decrypt revealed the sender; this caps total
  decrypt work up front.
- NostrStore reads tolerate a poisoned lock (unwrap_or_else into_inner) so a
  single panic can't cascade into taking down all nostr storage for the session.
- Avatar upload/delete reuse the service's keys directly instead of
  round-tripping the secret through a plaintext nsec String.

34 lib tests green.
2026-06-11 23:23:26 -04:00
Claude 413746dde3 Build 21: harden the client — NIP-05 gate, hostname validation, avatar limits
From a security audit of our own nostr/identity code (no P0/P1 found; these
close the P2 hardening gaps):
- NIP-05: only goblin.st identities skip the "pay an unverified key?" gate.
  A third-party domain's well-known could point at any key, so those now route
  through the same confirm gate as a bare npub.
- NIP-05: validate the domain as a bare hostname before building the
  well-known URL — closes a path/host-smuggling (SSRF-over-Tor) vector.
- Avatars: decode server-fed bytes under explicit image Limits (<=1024 px,
  8 MiB) so a hostile or breached avatar host can't exhaust memory on the
  texture path.

34 lib tests green (incl. new hostname-rejection cases).
2026-06-11 22:55:09 -04:00
Claude c3b23dc1a7 Build 20: README credit, onboarding copy, centered balance hero
- README: credit Claude (Anthropic) for Goblin's development at the bottom,
  in place of per-commit co-authorship.
- Onboarding: replace the contradictory "Pay people, not addresses" card with
  "Send like a message" and reword the body in plain language (no more
  "plumbing"/"ciphertext"). The identity step now says you can rotate your key
  anytime to maintain your privacy.
- Home: center the balance hero so it lines up with the Pay amount and the
  empty-state below it.
2026-06-11 22:55:09 -04:00