1
0
forked from GRIN/grim
Commit Graph

628 Commits

Author SHA1 Message Date
2ro d5d1212a44 Goblin: fetch fiat rate live on view, kill the 48h price cache
The fiat subline now fetches the CURRENT rate over Tor only while the
balance is actually on screen, and never paints a stale number as if it
were live. No disk cache, no background timer: an idle or payment-
listening wallet never polls (battery).

- price.rs: in-session rate held for a 3-minute freshness window; viewing
  a stale/missing rate kicks a live refetch. New RateState (Fresh/Loading/
  Unavailable) replaces the Option<f64>; failures are tracked so the line
  says "unavailable" instead of spinning or showing an old value. Removed
  the disk seed and the persisted-rate write.
- config.rs: dropped the dead last_rate/last_rate_vs/last_rate_at fields
  and their read/write paths. Old configs still load (serde ignores the
  unknown keys); the fields are simply not written back.
- lib.rs: dropped the cold-start seed_from_disk call.
- GUI: balance hero fiat line renders a subtle "≈ …" while loading and a
  localized "rate unavailable" on failure, requesting a bounded repaint
  while a fetch is pending so the rate pops in on-screen. Pay/send amount
  preview shows nothing until a fresh rate lands.
- New goblin.home.fiat_unavailable string across all six locales.
- Tests: freshness-window bounds, is_fresh boundary, classify state
  machine (fresh/loading/unavailable), and config compat for the removed
  fields.
2026-07-05 04:38:24 -04:00
2ro 3db6375459 Goblin: apply node selection to the running wallet, no silent zero
Selecting a node in Settings > Wallet > Node moved the checkmark and
persisted the choice, but the live session never switched: the node
client is baked into the wallet instance at open time and was only
rebuilt on the next unlock, so a running wallet kept polling the old
(often dead) node. Owners onboarded on an older build still carry the
retired grinnode.live default in connections.toml, which now 502s, so
the wallet showed a stuck zero balance with no way to recover short of
a force-restart.

GRIM's own connection-settings UI reconnects on change (close + reopen);
the Goblin node picker only called update_connection, so this was a
Goblin regression, not inherited upstream.

- Wallet::reconnect_node swaps the live node client's URL + secret in
  place via w2n_client (no close/reopen, so no password re-entry),
  updates the runtime connection so the UI reflects the switch at once,
  clears the stale sync error, and wakes the sync thread to refresh
  against the new node. Wired into all three picker actions.
- node_url_secret factored out of create_node_client so open and live
  reconnect always resolve the same node for a given config.
- balance_hero no longer renders a bare 0 during a node outage: a pure
  balance_subline state machine picks updating / can't-reach-node /
  last-known-balance, so an unreachable node reads honestly instead of
  looking like an emptied wallet. New goblin.home.balance_stale string
  in all six locales (drift green).
- Unit tests for the balance state machine.
2026-07-05 03:38:08 -04:00
2ro a7c2443f3b Goblin: proof-on-request payments (wallet side of the frozen contract)
Implements the Goblin-wallet half of the proof-on-request architecture
(magick payment infra spec section 4/5): the buyer's wallet threads a
native Grin payment proof per transaction, off by default and on only
when the scanned pay URI asks for it, and auto-delivers the proof to the
market and watcher on finalize. Person-to-person sends are unchanged.

W1 pay-URI parser (payuri.rs): PayUri gains proof/order/notify, each
fail-closed (a bad value drops to None and never blocks the payment).
proof is a grin1/tgrin1 slatepack-address shape check, order is an opaque
control-stripped routing key capped at 64 bytes, notify is an npub shape
check. Unknown-param forward-compat and the magick interop tests still
hold. 12 new parser tests.

W2 proof threading: WalletTask::NostrSend carries proof/order/notify; the
handler re-parses proof authoritatively into a SlatepackAddress and calls
w.send(a, Some(addr)) instead of the hard-coded None, setting
payment_proof_recipient_address. The order handle, watcher npub, and
amount persist in TxNostrMeta (new serde-default fields) before dispatch,
so a crash between send and finalize loses nothing.

W3 review indicator: the send-review screen shows a small "proof included"
row when proof mode is active. New t!() keys row_proof / row_proof_val in
all six locales (drift test green).

W4 proof delivery on finalize: when a finalized SEND was sent in proof
mode, the wallet publishes the two contract events: a plain unencrypted
kind-17 "payment sent" receipt to the app relays (payment-request tag =
order handle; proof and kernel deliberately omitted to avoid leaking the
buyer's sender address; flips the page to "detected", never "paid"), and
a NIP-59 gift-wrapped kind-17 rumor to the notify npub carrying the full
proof JSON plus a kernel-excess tag. Both are enrolled in the existing
crash-safe reconcile pass and retried until a relay accepts them.
wrapv3 gains wrap_kind() to gift-wrap an arbitrary rumor kind.

Matching is by the payment-request (invoice number) tag alone; the wallet
never learns magick's internal orderId. No merchant-side auto-receipt is
built: the rewritten spec has the buyer publish on finalize instead.
2026-07-05 03:01:04 -04:00
2ro ae0f36d287 Goblin: goblin: payment deep link opens straight to send review
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.
2026-07-05 01:53:18 -04:00
2ro 9f019edfeb Goblin: fix desktop clipboard (copy/paste lost the selection)
On desktop the Paste button in the wallet-restore seed entry did nothing, and
the mirror copy on the new-wallet creation screen was equally broken: a copied
recovery phrase never made it to the clipboard.

Root cause (inherited from GRIM, not a Goblin regression — the two clipboard
impls are byte-identical): copy_string_to_buffer / get_string_from_buffer each
created a fresh `arboard::Clipboard` and dropped it the instant the function
returned. On Linux (X11 AND Wayland) the clipboard selection is owned by the
live Clipboard instance, so dropping it immediately released ownership and the
copied text vanished before it could be pasted. Reproduced with arboard 3.6.1
on a Wayland session: fresh-instance-per-call read back empty; one persistent
instance across set+get read back correctly.

Fix: keep ONE long-lived `arboard::Clipboard` on the Desktop platform (behind a
Mutex, since the trait methods take &self and arboard's take &mut self), so our
process stays the durable selection owner. Also stop unwrap()-panicking on a
clipboard backend error — a failed copy/paste now logs instead of crashing.

Every clipboard button in the app routes through this one platform pair
(onboarding paste/copy, GRIM creation paste/copy, proof, slatepack message
entry, tx/receipt copy, receive copy, npub/nsec/backup copy, the shared
TextEdit paste), so this single change repairs the whole class at once.

Verified end-to-end: an #[ignore]d clipboard_roundtrip test drives the real
Desktop impl on the live session clipboard and asserts a 24-word phrase
survives copy -> paste (run with --ignored). It passes on Wayland now and would
fail under the old pattern.
2026-07-05 00:41:33 -04:00
2ro 093c5014ef Goblin: GoblinPay QR scans straight to review
When a scanned QR is recognized as a full GoblinPay payment payload (a
recipient key that decodes locally to a concrete pubkey AND a validated
amount), skip the recipient discovery/search step and open the review screen
prefilled with recipient, amount and memo. The merchant checkout QR already
carries everything needed to pay, so re-walking the recipient search was
redundant. The review screen still confirms the send unchanged (the user
holds-to-send); nothing auto-sends.

recognize_scan classifies the payload:
- Review: nprofile/npub/64-hex recipient + amount -> straight to review. A
  cheap LOCAL contact lookup supplies a friendly name; otherwise the short
  npub (no network resolution on this path). nprofile relay hints carry
  through as delivery targets.
- Search: a name/@handle (needs NIP-05 resolution), an amount-less key, or
  non-nostr text -> the pre-existing search-box path, with any amount/memo
  still prefilled.

This covers every scanner entry into the send flow (search-field scan icon
and the header scan-to-pay screen), so it works from anywhere the payment
scanner is reachable.

Unit tests on the pure recognize_scan / decode_recipient_key:
GoblinPay-uri-with-amount -> Review; key-without-amount -> Search;
name+amount -> Search; non-nostr -> Search; key decode variants.
2026-07-05 00:34:59 -04:00
2ro 65d8d0f7bd Goblin: language-aware news panel
Show the newest news article whose language matches the wallet's active
locale, falling back to the newest English article when none exists.

Language detection per article, in priority order:
1. An event language tag: NIP-32-style ["l","<code>","ISO-639-1"] or the
   bare ["l","<code>"] / ["lang","<code>"] (code is the tag's 2nd element),
   extracted in handle_news and persisted on NewsItem.lang.
2. A trailing "[xx]" marker on the title (case-insensitive, xx = ISO 639-1
   two letters), e.g. "2026-07-05 Welcome to Goblin [de]".
3. No marker anywhere = English.

Selection happens at the data layer (news_latest): the store stays
language-agnostic (still newest-per-d-tag, cap 8, edit-in-place). Among all
cached articles, filter by detected language, take the newest by created_at;
the "[xx]" marker is stripped from the displayed title. The app locale is
folded to its ISO 639-1 primary for matching (zh-CN -> zh).

The GOBLIN_FAKE_NEWS debug hook now injects an English + German sample so the
panel selection can be screenshotted per locale.

Unit tests: tag detection (both shapes), title-suffix detect + strip,
no-marker-means-English, zh-CN folding, locale match + English fallback,
newest-within-language-slice.
2026-07-05 00:34:47 -04:00
2ro d4dcbb115f i18n: translate lagging fallback strings and localize settings username hint 2026-07-04 23:13:41 -04:00
2ro ba8e81ef5f Goblin Build 136 - home news panel, faster send confirmation
Home now shows the latest post from the Goblin news key (kind 30023) in
a card with tappable links; the panel stays hidden until a post is seen.
Desktop Home widens to use the available space. Send confirms faster.
2026-07-04 20:07:13 -04:00
2ro 89791793ed Goblin: dedupe balance sync indicator
Remove the Build 135 balance-hero "Updating…" line. During a routine
sync it duplicated the header node card's "Syncing" status, so a funded
wallet showed two updating indicators at once. Keep the established
"Balance updating…" line (zero-balance / first-sync case) and the header
status as the single sync indicators. Drop the now-orphan
goblin.home.updating key from all six locales (i18n drift green).
2026-07-04 12:10:52 -04:00
2ro e8d71afc7e Goblin Build 135 - Tor credit, balance updating indicator, Nym sweep
- Third-party credits: replace the "Nym mixnet" row with "Tor (arti)" (0.43,
  linking the arti repo), matching the other credit rows' hardcoded style. No
  locale strings existed for it (credit labels are hardcoded, not localized).
- Balance: show a quiet "Updating…" line under the balance while the node is
  still warming, reusing the existing wallet.syncing() signal and the
  balance-updating line's muted style; it clears once the node is synced. New
  t!() key goblin.home.updating added to all six locales (i18n drift green).
- Nym sweep: Cargo.toml package description "Nym mixnet" -> "Tor". The only
  other user-facing Nym reference was the credit above. Remaining references
  are the deliberately-dormant `nym` feature/module and internal code comments,
  left as-is; README and all locale values were already Tor.
2026-07-04 06:15:44 -04:00
2ro 30c0ed9a12 Goblin Build 133 - Tor transport (replaces the Nym mixnet)
The wallet's private transport moves from the Nym mixnet to embedded Tor
(arti, copied from GRIM's engine): it dials the relay's pinned .onion, so
the relay never learns your IP, while the relay + NIP-59 gift-wrap hide the
rest - content, sender, and (via a relay-side randomized release) timing.
The Grin node stays on the clear internet as before.

Why leave the mixnet: the Nym free-tier bandwidth this depended on is being
removed upstream (the grant expires at UTC midnight; the paid path requires
holding NYM tokens), so a payments wallet can't stand on it. Tor is
unmetered, embedded in-process on mobile, faster where users wait, and
lighter on the battery.

Preserved intact: the confirm-before-sent guard, relay-gated readiness, and
the lazy warm-on-activity node polling. src/nym/ is feature-gated off (arti
and nym-sdk can't share one binary); full removal is a follow-up.
2026-07-04 03:35:29 -04:00
2ro 2478a7c08a ui: update banner shows the build number, not GRIM's frozen 0.3.6
The 'Update is available' fallback row rendered crate::VERSION (CARGO_PKG_VERSION = GRIM's 0.3.6) as the current version, e.g. '0.3.6 > build131' — a mismatched, GRIM-leaking string. Show the Goblin build number: 'build130 > build131'.
2026-07-03 13:40:22 -04:00
2ro c83771bbf8 gui: remove avatar name ring
Names never affect the avatar anymore: claimed and anonymous identities
render identically (picture or pubkey-seeded gradient). Drops
gradient_avatar_ringed/name_ring/is_named and the ringed rows in the
avatar_ring example sheet.
2026-07-02 22:31:06 -04:00
2ro 54344bd1d3 android: one-shot payment-requested notification (id=3)
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).
2026-07-02 22:19:23 -04:00
2ro aa39737d3b nym: fast interactive reads + faster reconnect (no money-path change)
Profile/username/NIP-05 reads were ~10s and the tunnel stalled 20s on a
dead gateway. Fixes:
- Profile/accepts/dm-relay fetches stream scoped to their dial set and
  return on the first matching event instead of waiting for every relay
  (or the full timeout) - the ~10s nprofile search.
- HTTP over the mixnet is tunnel-first, scoped-exit only as fallback when
  the tunnel is not up (NIP-11/price/name lookups are public data).
- Name re-verify interval 78s -> 6h (was a debug leftover churning reads).
- Discovery relay NIP-11 probes run in parallel, not sequentially.
- Tunnel build timeout split from the exit dial cap: build 20s -> 10s
  (env GOBLIN_NYM_BUILD_TIMEOUT) so a dead gateway is abandoned fast; the
  exit money-path dial stays 20s.
- Cold start brings the tunnel up first, then prewarms the exit once after
  publish (grant sequencing preserved).
- NIP-05 search bounded to 15s instead of hanging up to ~90s.

Money path (transport.rs, streamexit.rs) byte-for-byte unchanged.
2026-07-02 18:57:52 -04:00
2ro 989fd5b04a goblin: name avatars get a thin yellow outline, not a conic gradient
The username-seeded conic-gradient ring is replaced by a single thin,
light-yellow outline that hugs the avatar circle (image or grinmark
orb). It only appears for a claimed name (never a bare npub) and simply
signifies 'this identity has a name' - no per-name color, no gap, no
gradient. Drops the now-dead ring_params seed.
2026-07-02 15:41:42 -04:00
2ro acf9a140f6 goblin: scan an amount from a pay-URI QR (GoblinPay parity)
The recipient QR gains optional amount/memo query params:
  nostr:<nprofile>?amount=<decimal GRIN>&memo=<percent-encoded>
Scanning a GoblinPay checkout QR now prefills the amount (and the send
note) instead of only the recipient. Bare nostr:<nprofile> is unchanged.

The parser (src/nostr/payuri.rs) is pure and fail-closed over untrusted
scan input: amount is accepted only if the wallet's own
amount_from_hr_string parses it strictly positive; memo is percent-
decoded, control-stripped and 256-byte capped; only the nostr: scheme
unlocks params; 4096-byte cap; embedded NUL rejected; any problem
degrades to recipient-only. PREFILL ONLY - the picker resolver and the
amount/review confirm still gate every send; nothing auto-advances.
2026-07-02 15:25:57 -04:00
2ro db793bc13d goblin: log in to nostr apps with your nsec (Advanced -> Nostr key)
Advanced gains a password-gated Nostr key card: reveal the wallet's nsec,
Copy it, or show it as a QR. Scanning that QR (or pasting the copied
nsec) into a nostr app's private-key login - e.g. magick.market - signs
you in with the same identity the wallet uses. The nsec is derived on
demand behind the wallet password and never persisted; wrong password
cannot leak it. Six advanced.* strings added across all six locales.
2026-07-02 15:03:32 -04:00
2ro 71bf9b90e5 Goblin Build 123: fast relay connect after app updates
Fix: a relay-pool cache written by an older build parses fine but lacks
the scoped-exit addresses, which silently disabled the fast money path
for up to 7 days after an update - relay connects rode the slow public
path for minutes. The cache is now ignored and replaced when it lacks
exits, and the pinned pool takes over immediately.

Fix: the scoped-exit mixnet client now prewarms at cold start, so the
sequencer's head start is real (it previously waited on a client that
nothing had started until the first relay dial).

Build: wallet submodule repinned to the upstream grim branch tip
(c2db754) - the previous pin was deleted upstream, breaking CI checkout
and fresh clones. Policy: submodules stay pinned to GRIM's sources.

Lean: drop verified-dead code (avatar upload pipeline, legacy UDP DNS
path, legacy watchdog, duplicate TLS config, one-use error type, dead
store helpers).
2026-07-02 14:34:13 -04:00
Goblin 12f78f3af7 send: allow pasting an npub in the Send-to field (issue #1)
The recipient field had a QR-scan button but no paste, so on Android there
was no reliable way to paste an npub. Add the paste button (same one the
slatepack field uses).
2026-07-02 09:56:40 -04:00
Goblin 78e141f319 avatar ring: sit around the orb with a clear gap, thinner
The username ring overlapped the gradient orb's edge. Inset the orb and
add a real gap so the ring sits around it without touching; thin the ring
(size*0.045) since it no longer needs to overlap.
2026-07-02 09:35:56 -04:00
2ro c701f0f480 Floonet scoped Nym exit + NIP-44 v3 + wallet polish
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.
2026-07-02 04:17:59 -04:00
2ro 1e8e0f6526 settings: point the GRIM link at the live upstream (GetGrin/grim), not the stale ardocrat fork 2026-06-17 23:06:52 -04:00
2ro f0b5410c13 goblin: approving a payment request goes through a hold-to-accept review
Tapping Approve on an incoming request no longer pays immediately — it opens a
full-surface review (who's asking, amount, note, live network fee, privacy,
delivery) with a hold-to-accept gesture, mirroring the send review. Paying a
request is a spend, so it should confirm like one. The NostrPayRequest is
dispatched only when the hold completes; decline is unchanged, and an
over-balance request disables the accept. New goblin.request.review_title /
hold_to_accept / hold_accept_hint across all six locales (drift green).
2026-06-17 15:08:51 -04:00
2ro 991670d863 nostr: pause the @name re-verify sweep while the app is backgrounded
The 78s sweep fired regardless of whether anyone was looking, spending mixnet
round-trips in the background. Gate it on a frame heartbeat: the GUI stamps
crate::mark_frame() each draw (with a light ~2s repaint cadence so it stays
fresh while visible); when the app is backgrounded eframe stops drawing and the
stamp goes stale, so crate::app_foreground() reads false and the sweep skips.

The skip deliberately does NOT advance last_name_sweep, so the first tick after
the app returns to the foreground runs the sweep immediately — catching up on
resume rather than waiting out another full interval. Heartbeat lives at the
crate root so nostr reads it without depending on the gui module.
2026-06-16 22:58:07 -04:00
2ro d60e71d1e0 onboarding + nostr: healthy default node, claim feedback, identity import, resolve payer name
Four field-reported issues from a fresh install + a friend payment:

- Default node was grincoin.org, whose foreign API was returning "rpc call
  failed" — onboarding sync died with an un-retryable error. Lead the node
  list with the verified-healthy api.grin.money (external.rs) and use it as the
  onboarding default (was grincoin.org); grincoin.org stays in the list.

- Claiming a username gave no feedback and the identity card kept showing the
  npub. The card now shows the @name + a seal check once claimed, and a clear
  "name is yours" success card replaces the claim form before Open wallet.

- A returning user who restores a seed gets a fresh random nostr key, so their
  old @name couldn't come back. Offer "Import it" in the identity step: paste an
  nsec or pick a .backup file (reuses the wallet password just set) to keep the
  existing key + username.

- The requester side of a request never resolved the payer's @username — the
  FinalizePost ingest arm skipped ensure_contact/resolve_contact_identity, so a
  completed request showed a bare npub for the payer. Resolve on finalize like
  every other ingest path.

i18n: claimed_title/claimed_blurb + import_existing/import_title/import_blurb
across all six locales; drift test green.
2026-06-16 18:50:19 -04:00
2ro 11033b93fe Federation, note modal, save-to-device, onboarding fixes + UI polish
- 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.
2026-06-16 03:22:08 -04:00
2ro 7eb0683646 docs: drop the Cash App comparison; remove avatar mentions from the README
Rephrase the README as 'pay-by-username' and scrub the Cash App name from
code comments. Also drop the leftover 'hosted avatar'/'avatars' wording —
identities use generated identicons, not uploaded pictures.
2026-06-16 01:03:05 -04:00
2ro 9dba2163fa ui: profile/contacts, requests spinner, receipt fixes, backup file, send + advanced
- Republish kind 0 right after claiming a username (was invisible until restart).
- Request card shows a 'Paying…' spinner instead of a dead greyed button.
- Receipt: count confirmations 1/10…10/10 (was stuck at 0/10, jumped to done
  at one block); hide the network-fee row on received payments.
- Settings: one 'Back up to a file' flow (GOBLIN-*.backup) replacing copy-nsec
  / copy-JSON; import accepts a .backup file via the native picker.
- Advanced: 'Run your own node' opens the node-connection page (incl. an
  integrated-node option); Repair confirms in accent; Restore warns in red.
- Send: drop the 1/10/100/Max chips; Note becomes an Add-note editor.
- Remove the dead profile-picture upload UI and scrub picture wording.
- Localize all new strings across 6 locales; drift test green.
2026-06-16 00:32:02 -04:00
2ro a35fb7956c Android: dark status-bar icons on the yellow GRIM/onboarding header
In dark theme the wallet list, app settings, wallet creation and onboarding
leave the bright accent-yellow title_panel_bg showing under the status bar,
but status_bar_white_icons() returned the dark-theme default (white) — white
icons are illegible on yellow. Force dark icons on every non-Goblin-surface
screen (the Goblin surface covers the inset and sets its own per-tab flag).
2026-06-15 21:42:43 -04:00
2ro 6a0c2565b5 Review page: show live network fee for the amount (priced like GRIM's send modal) 2026-06-15 21:18:34 -04:00
2ro b7c3b95f51 Copy buttons: haptic tick + transient 'Copied' confirmation 2026-06-15 21:15:26 -04:00
2ro 22292ef79c Onboarding skips node step; internal node moves to Advanced; WALLETS->GOBLIN
- Onboarding goes Intro -> wallet setup directly (connected to a public node
  instantly); the node-choice step is retired.
- External node settings stay in Settings; 'run your own node' (integrated) now
  lives in the Advanced submenu.
- Wallet-list: title reads GOBLIN (was WALLETS), dropped the redundant GOBLIN
  wordmark under the mark, build number kept but smaller.
2026-06-15 21:07:42 -04:00
2ro 9768de2fbd Add manual 'Cancel payment' to reclaim a stuck outgoing send
A Goblin payment locks the sender's outputs until the recipient replies (S2)
and we finalize+post. If the recipient never connects to nostr, the funds stay
locked until the 24h auto-expiry. This adds a manual Cancel that reclaims them
on demand (after a 10-min grace, or immediately if the send never reached a
relay), marks the payment Cancelled, and best-effort voids it to the recipient.

- WalletTask::NostrCancelSend: authoritative tx lookup; refuses if already
  finalized/confirmed (race); marks meta Cancelled BEFORE cancelling the grin
  tx; serialized with nostr_finalize_post via a per-service lock so a cancel and
  a concurrent S2 finalize can't both commit.
- nostr_finalize_post returns Ok(false) (skip, no retry/re-post) when the tx is
  cancelled or the meta is Cancelled — covers the tx-list cancel path too.
- decide() already drops a late S2 on a Cancelled meta (new unit tests assert
  it); recipient-side void marks a received payment Cancelled for display WITHOUT
  deleting the output (a malicious sender could void-then-post otherwise).
- Void-before-S1 ordering handled via a (slate,sender)-bound marker.
- Receipt: tap-twice 'Cancel payment' with caveat + outcome notice; honest
  'Waiting for X to receive…' label; first-class Cancelled status. 6 locales.
- cancel_grace_secs config (default 600).
2026-06-15 19:34:44 -04:00
2ro 2e6cff9eeb Receive screen: copy/share the npub, never a grin address
The right button copied the grin1 slatepack address and the left copied the
nprofile — both wrong for 'how people pay me'. Now: left = Share a friendly
'Pay me on Goblin (goblin.st) — npub1…' message, right = Copy the bare npub.
New receive.copy_npub / receive.share_message keys across all six locales.
2026-06-15 17:28:26 -04:00
2ro ba504aa266 Cap claimed names at 20 chars to match the name authority
The nip05d authority now enforces a 3..=20 length; align the wallet's claim
validation (onboarding + settings) and the 'Names are 3-20 chars' hint across
all six locales so the wallet never offers a name the server will reject.
2026-06-15 16:12:26 -04:00
2ro 5005d7cb2d Build 82: names without @, wallet management + Advanced, list header, sturdier receive
- Drop the displayed @ prefix everywhere (identity picker, slatepacks page, all copy); @ stays internal for lookups/avatars.
- Settings: move wallet management to the foot — Switch wallet (deselect, stays unlocked), Lock wallet, and a new Advanced page mirroring GRIM's recovery tools (repair, restore-from-seed, reveal recovery phrase, delete).
- Restore the wallet-list title header (reachable settings gear, Pay-screen accent yellow).
- Transport: a transient receive/finalize failure no longer marks the gift wrap processed, so an incoming payment is retried on catch-up instead of being silently lost; finalize-post is now retry-safe (re-posts an already-finalized slate).
- Guard a latent panic on a short sender key in ensure_contact.
- Add more healthy public grin nodes (mainnet + testnet) for redundancy.
- Default first-run onboarding to the Instant connection (public node), shown first.
- Tidy leftover Tor remnants left from the fork.
2026-06-15 02:17:45 -04:00
2ro 96daed3c84 Build 81: show names everywhere, no @, tidier pay entry
Resolve a counterparty's @username on every interaction, not just incoming
requests — receives, sends and requests all kick off a verify-and-cache, plus a
one-time backfill on wallet open — so activity and the recent strip show names,
not bare npubs. Usernames now render WITHOUT the @ (kept internally for avatar
lookup); the recent row ellipsizes past 8 chars and centers the name under the
avatar, while activity shows the full name.

Pay screen: the numpad now sits above the note so the soft keyboard can't cover
it (and tapping the pad dismisses the note's keyboard), and a no-op key — a
second dot, a 0 on a leading zero, the 9-decimal cap — fires a short error
haptic instead of doing nothing silently.
2026-06-15 01:14:36 -04:00
2ro f715149302 Build 80: show the real balance and explain failed sends
The balance hero only showed amount_currently_spendable, so a wallet whose
funds were still confirming read 0 while GRIM showed the real total — the source
of the "Goblin says 0" confusion. It now shows the TOTAL (matching GRIM) with an
"X available - Y confirming" breakdown when some isn't spendable yet.

A send or approve that hits NotEnoughFunds (coins from a recent payment still
confirming, ~10 min) now says exactly that instead of a blank "Couldn't send",
and the Approve button no longer stays greyed forever — it un-greys on failure
with the reason shown, so the user can retry once funds clear. The relay-dial
cap for cross-relay delivery drops 12s to 6s so the first send isn't sluggish.
2026-06-15 00:21:10 -04:00
2ro af30af48e4 Build 79: deliver payments and profiles across different relays
A send/request to a counterparty on relays we don't already hold failed with
"relay not found": NIP-17 publishes to the RECIPIENT's relays, but send_*_to
rejects any relay not in the pool. connect_relays() now adds and dials the
target's relays (from their kind-10050 or an nprofile/QR hint) before sending,
so the gift wrap actually reaches their inbox. Same fix lets profile/@username
lookup find a kind-0 that lives only on the target's own relays — fetch_profile
takes relay hints and dials them first. Sidebar handle font now scales with
length so short @names are legible (was a fixed, too-small 11px).
2026-06-14 22:47:02 -04:00
2ro f0b854171c Build 78: honest transport labels, request decline/cancel, NIP-05 on requests, full localization
Settings now says "Manual transaction" and the privacy row reads "Messages &
lookups" opening a new Network privacy page that tells the truth: messages,
names, price and avatars ride the Nym mixnet; the grin node connects directly.
README and lander updated to match.

Requests are messages, payments are final: declining a request now sends the
requester a void control message (NIP-17), a requester can cancel a request they
sent (cancels the local invoice and notifies the payer), and incoming requests
resolve the sender's verified @username instead of a bare npub. The Requested
amount on the success screen is centered. New NostrDecline/NostrCancel tasks and
a goblin-action control message carry it, bound to the stored counterparty.

Localization: every Goblin-screen string moved to t!() keys (370 keys) and
translated into de/fr/ru/tr/zh-CN, guarded by a key/placeholder drift test.
System-locale auto-detect now matches region locales like zh-CN.
2026-06-14 21:44:24 -04:00
2ro 0644807f51 Build 77: smaller sidebar avatar so the npub fits one line
Shrink the sidebar identity chip's avatar (36→28px) and handle (12→11px) to
free horizontal space, so the npub/handle stays on a single line.
2026-06-14 18:46:22 -04:00
2ro 8e1b3ce847 Build 76: split mixnet vs nostr status; tidy the identity chips
- The profile card drops the duplicate npub line and shows two statuses: the
  mixnet ("Connected over Nym", ~instant) and under it the nostr relay
  ("Connected to nostr" / "Connecting to relays…"). The relay connection is the
  slower step, not the mixnet.
- New cheap cached nym::is_ready() flag backs the mixnet status (no per-frame
  TCP probe), set when the SOCKS5 proxy comes up.
- Sidebar identity chip: smaller handle so the npub stays on one line, with a
  matching Nym/relay status.
2026-06-14 18:23:00 -04:00
2ro fb4f27a88f Build 75: no pairing by default; price back over the mixnet
- Pairing defaults to Off on first run: no conversion is shown anywhere and no
  price request leaves the device until the user opts into a pairing. Off is a
  picker option and the choice persists.
- The rate fetch goes back over the Nym mixnet (reverting Build 71's clearnet
  route) — it's opt-in and infrequent, so it stays private and doesn't get in
  the way of slatepacks. CoinGecko's free-tier rate limit (shared exit IP) rules
  out live/1s polling, so it keeps the slow cache.
- Small note on the Pairing page that rates fetch over the mixnet.
2026-06-14 18:08:23 -04:00
2ro e5d1f0c6db Build 74: Pay header — bigger black QR + profile avatar, consistent sizes
On the Pay screen the scan QR drops its dark puck (now a bigger black icon
directly on the yellow), the user's profile picture sits to its right as a link
to the settings/profile page, and the goblin mark is sized to match — all three
controls about 40px.
2026-06-14 16:07:53 -04:00
2ro f5c449463b Build 73: chromeless Pay-screen navbar
On the Pay surface the 3-item bar drops its floating pill + shadow — Wallet,
the center ツ, and Activity sit dark directly on the yellow, Cash App-style.
Other surfaces keep the floating pill.
2026-06-14 15:41:44 -04:00
2ro 7e3f335e79 Build 72: fix the Pay-screen brand marks
Correct the Build 71 misread of the design notes:
- Amount keeps the ツ unit mark (revert the goblin mark next to the number).
- The goblin mark replaces the "Pay" title at the top-left (tinted dark to read
  on the yellow surface).
- The bottom tab-bar strip turns yellow on the Pay surface, like the body.
- (Dark status-bar icons on the yellow surface kept from Build 71.)
2026-06-14 14:51:17 -04:00
2ro 726e96130c Build 71: clearnet price + update check, Pay-screen UI polish
Following upstream Grim's posture for non-sensitive metadata: the fiat price
preview and the update check now go direct over HTTPS instead of the mixnet, so
the price shows promptly without waiting on the mixnet bootstrap. Payments,
relays and identity stay mixnet-only.

- price.rs: fetch the rate over clearnet (plain reqwest), no proxy.
- update check: on by default like Grim, repointed at Goblin's own GitHub
  releases, build-number aware (is_update compares buildNN), Goblin asset names,
  GitHub User-Agent header.
- Pay screen: status-bar icons go dark on the bright yellow surface
  (status_bar_white_icons honours a per-frame yellow-surface flag); the hero
  amount shows the goblin mark as its unit in place of the ツ glyph.
2026-06-14 14:21:39 -04:00
2ro 851ae1c565 Build 70: refresh README + clean stale comments for the public release
README: drop the removed nym-socks5-client sidecar story (the SDK is linked
in-process now), add the in-process build steps + the manual-slatepack feature.

Comments: replace stale "sidecar" wording with the in-process SOCKS5 proxy,
drop leftover Tor references (Goblin routes over Nym), and trim the chattiest
working-notes to terse rationale.
2026-06-14 13:02:03 -04:00