1
0
forked from GRIN/grim
Commit Graph

1070 Commits

Author SHA1 Message Date
2ro ce23214d98 nostr: Connected flag tracks the fast relay-live probe, not the 30s catch-up fetch
Symptom: after 'Save & reconnect' of the relay list, the home/onboarding UI sat on 'Connecting relays…' for ~30s even though the relays had physically reconnected over the exit in ~2-4s.

Cause: on restart the service clears connected=false, then the UI flag was only restored AFTER publish_identity (serial, untimed per-event sends) AND a catch-up fetch_events_from bounded by FETCH_TIMEOUT=30s. One relay slow to EOSE pinned is_connected() false for the whole window while the connection was already usable. A separate FAST probe task already detects first-relay-Connected at 250ms poll (~2-4s) and reports relay-live to nymproc, but it did not touch the UI flag.

Fix: in that fast probe, when relays first report Connected (same point that calls report_relay_live), also set svc.connected=true. The indicator now tracks the real ~2-4s relay-up signal; publish_identity + the catch-up fetch continue in the background. Tradeoff (documented in code): a relay drop between the probe store(true) and the 2s status loop taking over wouldn't flip the flag for up to ~30s until the post-catch-up re-check re-syncs to reality — the same-order staleness as the old pessimistic gap, just optimistic; the transport watchdog still tracks real exit health independently.

Hardening: publish_identity's per-event send_event_to was untimed, so a stalled relay delayed the catch-up fetch and the kind:1059 subscription that follow it (real incoming-message latency). Each publish is now wrapped in tokio::time::timeout(SEND_TIMEOUT), mirroring dispatch_dm; on timeout it warns and continues to the next event, never aborting the sequence.

Audit: all readers of is_connected() were reviewed for the relaxed invariant (flag can now be true before the giftwrap subscription is established). gui/goblin/mod.rs and gui/goblin/onboarding.rs use it for display + repaint scheduling and to enable the claim-username button — claiming needs relays connected (which the flag now genuinely means), not the incoming kind:1059 subscription. wallet/e2e.rs uses it as a test precondition with downstream waits of 900s/2400s and relays replay stored gift wraps on subscribe, so it still converges. No reader treats is_connected() as 'safe to receive now', so no separate ui_connected flag is needed.
2026-07-03 15:39:02 -04:00
2ro e6e262009e nym: race two gateway connects on the cold-start path (bound the lottery tail)
The read-tunnel cold/auto path drew ONE random entry gateway; a dead draw blocked connect_to_mixnet() until run_tunnel's 10s cap and consecutive dead draws stacked into a 15-35s tail (measured 6/15 cold starts). With no warm gateway hint, build_tunnel now races TWO ephemeral gateway connects (identical anonymity cfg, ephemeral keys) via connect_gateway_racing and takes the first up; the loser is reaped (disconnected if connected, dropped if pending) so no gateway session leaks. Only the gateway handshake is doubled - the exit/IPR is still built ONCE on the winner. Warm reconnects (cached gateway hint) stay single-build. Money path (streamexit.rs) untouched.

Verify before release: emulator cold-start timing (~15 trials) shows the tail bounded, no session/task leak across many cold starts, warm path unaffected.
2026-07-03 13:52:26 -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 d95efef896 linux: build_release.sh produces a correct arm AppImage (per-arch runtime)
The arm path hardcoded ARCH=x86_64 and the x86_64 type2 runtime, so a
`build_release.sh arm` run produced a broken AppImage (x86_64 runtime wrapping
an aarch64 binary). Derive ARCH and the runtime file from the target arch, and
have toolchain.sh fetch runtime-aarch64 alongside runtime-x86_64. This restores
GRIM's linux-arm AppImage release type; verified the output is an ARM aarch64
AppImage.
2026-07-03 13:22:47 -04:00
2ro 698806421f deps: nip44 -> crates.io 0.3.0 (drop the ../nip44 sibling requirement)
nip44 is now published, so depend on the registry crate instead of the local
path checkout. A fresh checkout (the Mac included) no longer needs ../nip44
cloned next to goblin - that missing sibling was the reported build failure.
Byte-identical source (0.3.0 was published from this tree); cargo check --lib
green. nym stays a path dep (private fork, not on crates.io).
2026-07-03 11:56:40 -04:00
2ro 61545b767d android: version tracks GOBLIN_BUILD, not GRIM's frozen 0.3.6
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.
2026-07-03 11:47:38 -04:00
2ro 87f82eb61e nym: race the exit-liveness probe across stable targets + rounds
The single-target, single-shot probe(1.1.1.1:443) was the read tunnel's
liveness gate AND its keepalive. On a slow/unlucky exit->1.1.1.1 path it
false-declared healthy tunnels DEAD, which rejected freshly-built tunnels and
reselected forever ("Connecting to Nym" for minutes or never) and condemned
working tunnels via keepalive. Now it races two anycast targets (1.1.1.1,
9.9.9.9) over two rounds, mirroring the DoT/DoH resolver racing above it: a
tunnel is DEAD only if it reaches NEITHER across both rounds. Verified over 15
cold starts: 15/15 connected, zero DEAD-EXIT, zero hangs (was the sole cause of
the never-resolves).
2026-07-03 11:15:54 -04:00
2ro b3165c3964 relays: default DM relay -> relay.floonet.dev (relay.goblin.st retiring); refresh Cargo.lock for nip44 0.3.0 2026-07-03 03:48:31 -04:00
2ro 4d1db9cb28 chore(relays): drop retiring relay.goblin.st from the pinned pool
relay.goblin.st + nrelay.us-ea.st retire 2026-07-04 04:44 UTC. The live
gist pool already dropped relay.goblin.st; this removes it from the
compiled-in PINNED_POOL fallback so first-run/offline no longer ships a
dead relay. relay.floonet.dev remains THE primary dm+discovery+exit
(money-path) relay.

- pool.rs: remove the relay.goblin.st entry (12 relays now) and repoint
  the pinned-pool tests onto relay.floonet.dev (counts 13->12, dm 11->10,
  discovery 4->3; the dm-membership and exit assertions). Synthetic
  parsing-logic fixtures that name relay.goblin.st are left untouched.
- wallet/e2e.rs: refresh the money-path E2E harness doc comment onto
  relay.floonet.dev (the RELAY_A/RELAY_B consts already point there) so
  the ignored on-chain test documents a live relay after retirement.

Validated: cargo test --lib pool (5 passed), cargo build ok.
2026-07-03 03:44:03 -04:00
2ro 138785cf67 test(payuri): cover the magick.market checkout pay-URI contract
Add round-trip tests asserting a realistic magick.market checkout URI
(nostr:<nprofile>?amount=<decimal GRIN>&memo=MM-<hex>) parses to the exact
recipient / amount / memo. This guards the magick.market <-> Goblin pay-URI
contract: magick converts its internal integer nanogrin to the decimal-GRIN
`amount` string the wallet's amount_from_hr_string accepts, and carries the
opaque invoice number as the memo. Test-only; the parser is unchanged.
2026-07-03 03:08:38 -04:00
2ro 9caa2b6809 nym: warm connect from caches + instant price + first-read probe
Cold connect gets a fast path: the last-known-good entry gateway and IPR
are persisted (choices only, keys stay ephemeral) and tried first on the
next launch - measured 4.4s to tunnel-ready vs 5.6s cold, and no dead-
random-gateway lottery. Cached hints self-clear on failure and fall back
to auto-select.

The price appears instantly: the last fetched rate (under 48h) paints on
the first frame and a live fetch fires the moment the tunnel is ready
instead of waiting for the balance screen. The eager fetch doubles as an
end-to-end probe: if the first reads all fail while the tunnel claims
ready, the exit is condemned and reselected in seconds instead of
minutes. warm_up now runs first at startup.

Money path (streamexit.rs, transport.rs) untouched; ponytail sweep
verified all fallback paths and gates green.
2026-07-02 23:24:39 -04:00
2ro b00719f2f9 ci: fetch nip44 in the push-triggered Build workflow too
release.yml got a fetch-nip44 step alongside fetch-nym (ae4306f), but the
push-triggered build.yml (Linux/Windows/MacOS Build) never did, so every
push has been failing cargo build with "failed to get nip44 as a
dependency ... No such file or directory" once nip44 became a path dep.
2026-07-02 23:01:38 -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 23bb845689 nym: major transport speedup (throughput + reuse), money path unchanged
The in-process smolmix tunnel was far slower than the old SOCKS5 model.
Fixes:
- smolmix TCP buffers 8KB -> 256KB and burst 1 -> 64: bulk throughput
  ceiling rises ~32x (was capping relay backfill and JSON reads at a few
  KB/s).
- Read tunnel runs a high-traffic mixnet profile (cover traffic off, higher
  send rate, fewer reply SURBs) for lower latency; per-hop mix delays kept.
  The scoped-exit money-path client is separate and unchanged.
- HTTP over the mixnet now reuses connections (keep-alive pool) instead of a
  fresh TCP+TLS+HTTP handshake per request - fixes slow price and username
  reads.
- DNS prewarm no longer skips on cold start and serves stale-while-revalidate
  for known hosts, so a dial never blocks on DoT/DoH.

Money path (streamexit.rs, transport.rs) byte-for-byte unchanged.
2026-07-02 19:57:35 -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 5733b9a894 update: fix in-app update download names (android-arm, linux tar.gz)
The in-app updater built the ARM Android download as goblin-<tag>-
android-arm.apk and the Linux download as .AppImage, but releases ship
goblin-<tag>-android-arm.apk (this build on) and a linux .tar.gz. On an
older release the ARM apk was named -android.apk, so the in-app update
404'd. Releases now name the ARM apk -android-arm.apk and the updater
targets the tar.gz, so Android and Linux in-app updates resolve.
2026-07-02 16:59:07 -04:00
2ro 8f1a955f9a docs: remove internal planning/meta docs, keep user and technical docs 2026-07-02 16:54:45 -04:00
2ro ae4306febe ci: fetch the nip44 crate for release builds
Adds a fetch-nip44 composite action (clones 2ro/nip44@v3 into ../nip44)
and runs it alongside fetch-nym in the linux/windows/macos jobs, so the
`nip44 = { path = "../nip44" }` dependency resolves on the runners.
2026-07-02 15:41:42 -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
2ro 5869ff78be android: drop the notification enable-node action; re-enable macOS release CI
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.
2026-07-02 13:38:40 -04:00
2ro 300d9cea4c nym: restore scoped exit as fast money path + relay.floonet.dev cutover
The 180s cold connect was the public-IPR path (nested TCP over the mixnet) plus
the public-exit lottery -- NOT the scoped exit, which was removed by mistake in
3372202. Restore the co-located MixnetStream exit: a relay connects in 0-2s over
it (measured), vs 15-180s over the tunnel.

- Cold-start sequencer: streamexit::is_ready + a bounded EXIT_HEAD_START gate in
  nymproc so the exit client claims its Nym bandwidth grant before the tunnel,
  avoiding two-client serialization (~1min otherwise). No SDK surgery.
- Pin relay.floonet.dev as the primary money-path relay (with its co-located
  exit) in PINNED_POOL; keep relay.goblin.st as a secondary through transition.
- E2E: a funded 0.1 GRIN payment finalizes in 6s over the exit across two
  different Grin nodes (grincoin.org, main.gri.mw).
2026-07-02 12:50:33 -04:00
Goblin 337220299f nym: remove the dead scoped-exit egress
The exit is unpinned everywhere, so the streamexit module + the exit_for/
exit_connect forks in transport + http were dead code. Remove them; the
wallet's only mixnet path is the smolmix tunnel.
2026-07-02 10:06:50 -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 bb2e8602ff relay pool: drop the scoped-exit pin (convenience first)
The money-path exit ran a second mixnet client whose cold bootstrap blocked
first-connect ~100s per session. Unset the exit so the wallet reaches
relay.goblin.st over the fast smolmix tunnel (still fully over the mixnet).
Privacy backbone unchanged; the exit stays deployed for a non-blocking
redesign later.
2026-07-02 09:48:37 -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 9262d7429b nip05: drop the name-transfer client — names are never transferred
The name service no longer exposes /api/v1/transfer (removed server-side), and
the model is release-and-reclaim, not transfer: on a key rotation you release
the old name and re-register (or import your existing identity). Removed the dead
nip05::transfer() client fn (it had no callers) and the "transfer" wording in the
README.
2026-06-17 22:10:35 -04:00
2ro 36e63d4751 sync upstream Grim: bump node + wallet, adapt tx_log_iter
Upstream Grim advanced past the fork base with a node + wallet version update
(b51a46b → its "node + wallet: update to latest versions"). Bumped both
submodules (node → bce5a71, wallet → c2db754) and applied the one source
adaptation that update requires: `tx_log_iter()` now yields Result items, so the
three call sites filter Ok + unwrap before use. The upstream Tor/arti-0.43 commit
is skipped — Goblin removed Tor entirely.
2026-06-17 22:10:25 -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 84cc9d663b docs: README no longer claims an avatar service — avatars are client-derived
The identity service stores names + pubkeys only; avatars are rendered
client-side from the pubkey (npub gradient + first letter, else the Grin mark).
Dropped the stale "avatar service"/"avatar pipeline" copy and the "avatar
fetches" mention from the mixnet-traffic line.
2026-06-17 12:05:13 -04:00
2ro 2235e64bac build(windows): embed the Goblin icon into goblin.exe via winresource
The .msi shortcuts already carry the icon, but the bare goblin.exe had none, so
Explorer/taskbar showed the generic exe icon. build.rs now embeds
wix/Product.ico (the yellow Goblin icon) as the exe's application icon resource.

Gated to Windows hosts: winresource is a `cfg(windows)` build-dependency and the
embed fn is `#[cfg(windows)]` (with a no-op stub otherwise), so Linux/macOS/
Android builds don't compile or run it — other releases are untouched. The embed
is best-effort (warns, never fails the build) in case rc.exe is unavailable.
2026-06-17 09:43:49 -04:00
2ro 2b5cb8ad55 ci(windows): build a WiX .msi installer, like GRIM (not just a bare exe zip)
GRIM ships a Windows .msi; we were only zipping goblin.exe. The Windows release
job now runs cargo-wix against wix/main.wxs (the cargo-wix default template:
WixUI_Minimal + launch-after-install), producing a proper installer whose
shortcuts and Add/Remove-Programs entry carry wix/Product.ico — the yellow
Goblin icon. WiX 3 is taken from the runner or installed via choco; --no-build
reuses the release exe so the embedded GOBLIN_BUILD number is preserved. We
upload BOTH the .msi and the portable .zip (matching GRIM). Windows-only change.
2026-06-17 08:48:01 -04:00
2ro 1fdbd80282 icons: regenerate every platform icon from the canonical Goblin sources
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.
2026-06-17 02:36:50 -04:00
2ro d0cb76fa02 img: drop unused legacy/GRIM images
Remove six images with no code or build references — leftovers from the GRIM
era and superseded logo variants:
  cover.png            old GRIM marketing cover (paw logo, "Grin · Tor")
  grin-logo.png        old Grin/MW smiley
  logo.png, logo_light.png   GRIM wordmarks
  goblin-logo-256.png  superseded
  goblin-logo2-256.png superseded (the app uses goblin-logo2.svg + -48.png)

Kept (all referenced): icon.png (main.rs window icon), goblin-logo2.svg + -48.png
(in-app marks), goblin-icon.png (gen_icons.sh source). The in-progress icon
rework (goblin-mask*/goblin-icon-512 pruning, new goblin-mark-*.svg) is left
untouched.
2026-06-17 01:23:52 -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 55b78b78ef macOS app icon = Goblin gradient; keep a contact's name across a decline
- The macOS bundle still shipped the old GRIM paw AppIcon.icns. Regenerated it
  from Goblin-Logo-Gradient-SMALLER.png (the yellow-gradient goblin mark) as a
  proper multi-resolution PNG-icns (16–512), so Finder/Dock show the Goblin icon.

- Declining (or cancelling) a request never re-resolved the counterparty, so
  their @name could drop to a bare npub just because the request didn't go
  through. handle_wrap now re-resolves the counterparty after a void — cheap,
  authoritative via the by-pubkey reverse lookup, and a no-op for anonymous keys.
2026-06-16 22:15:43 -04:00
2ro 919cfcb71e nostr: resolve a contact's @name via authority reverse lookup first
The requester still saw a bare npub for the payer because resolving a name
depended on fetching the peer's kind-0 off a relay (a nostr REQ with tight
timeouts over the private transport) before it could verify the NIP-05. When
that fetch doesn't land, resolution never starts — even though the name server
knows the name perfectly well.

resolve_contact_identity now asks the home authority directly:
GET /api/v1/by-pubkey/{hex} → the active @name for that key, in one HTTP
round-trip, with no profile fetch. That answer is authoritative, so the name is
set verified immediately. The kind-0 + verify path stays as a fallback for
FOREIGN authorities (which the home server can't speak for) and is still what
CLEARS a released/reassigned name. New nip05::name_by_pubkey helper.

Pairs with the goblin-nip05d by-pubkey endpoint. Verified by me only as far as
compile + unit/i18n tests; the live two-party resolution is the owner's call.
2026-06-16 20:01:20 -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 24abc7e7b3 docs: TRANSACTIONS.md — full payment lifecycle, statuses, ingest guard, cancel/expiry/recovery 2026-06-16 12:55:51 -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 dfbd85c7b3 nostr: keep contact @usernames fresh; clear released/reassigned names
Cached names were verified once and never re-checked, so a contact who
released or changed their username kept showing the stale name forever.
Re-validate names on a 78s sweep (capped per tick to bound mixnet lookups):

- nip05::check — tri-state Verified/Mismatch/Unreachable, so we only clear on
  a definitive server answer (released, or reassigned to a different key),
  never on a network blip.
- resolve_contact_identity now re-checks names older than the freshness window
  and clears nip05 + nip05_verified_at on Mismatch (a user petname is kept);
  display falls back to the npub automatically.
- A periodic sweep in run_service re-verifies the stalest due contacts.

Tests for the tri-state parsing and the clear-keeps-petname logic.
2026-06-16 01:39:54 -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 313a14b82c wallet: contacts on send, request-approve states, .backup create/import
- Create/refresh a contact when you SEND (not just receive) so people you
  pay show up under Suggested and resolve their @name.
- Approve-request: set SENT on the Standard1 success path and fail_send on
  the error + non-payable paths, so the button never sticks greyed.
- create_nostr_backup() + import of the encrypted .backup envelope.
2026-06-16 00:31:50 -04:00
2ro 222f149fc2 nostr identity: single fully-encrypted .backup envelope
Add to_encrypted_backup / from_encrypted_backup: the secret key is the
password-protected NIP-49 ncryptsec, and the rest of the identity is
NIP-44-sealed to our own key — so a GOBLIN-*.backup file leaks no npub or
username, yet any Goblin wallet reopens it with the password.
2026-06-16 00:31:37 -04:00