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.
20 KiB
GRIM to Goblin deviations audit
Audit date: 2026-07-01 (supersedes and incorporates the Build 39 deviation audit of 2026-06-12).
Comparison snapshot:
- Goblin:
git.us-ea.st/GRIN/goblinat1e8e0f6, plus uncommitted Phase-0 UI work in the tree (avatar, back-nav, balance, notification edits, new locale keys,examples/avatar_ring.rs). Phase-0 changes are treated as intentional Goblin-side additive work, not drift. - GRIM:
code.gri.mw/GUI/grimlocal clone atee88415, which is exactlyorigin/master(0 ahead, 0 behind). - The two repos have separate git histories (no merge base), so this audit is a working tree directory diff, not a git diff.
1. Repo topology, corrected
Earlier notes described goblin/wallet as a plain vendored directory. That is wrong. Both grin
crates are real git submodules in Goblin (goblin/.gitmodules):
node/->code.gri.mw/ardocrat/node, checked out atbce5a714, working tree clean.wallet/->code.gri.mw/ardocrat/wallet, branchgrim, checked out atc2db754("fix: ci"), working tree clean.c2db754is exactly the tip oforigin/grim. So the vendored wallet is byte-for-byte an unmodified published upstream branch. Confirmed untouched.
GRIM pins the same wallet repo differently: its superproject HEAD records gitlink
5c54e7c (the tip of branch grim-staging), while the local grim/wallet checkout in this
workspace sits at 8847ee5 ("build: fix deps", a local commit on an older base; the clone is
shallow, which is why 8847ee5 looks parentless there). The two wallet branches are different
lineages of the same repo:
grimbranch (what Goblin pins): full history, mergesmimblewimble/master(a3e71a8) and carries extra fixes GRIM staging does not have (full_scan_fixtrio,840bde7lmdb backend migration,ff1238c/b197afflmdb no-panic fixes).grim-stagingbranch (what GRIM pins): squashed shallow lineage plus the 2026-06 Tor arti work.
2. Application source audit (grim/src vs goblin/src)
40 inherited files are MODIFIED, 7 units are ADDITIVE (Goblin-only), 3 units are REMOVED (GRIM-only). Everything checks out as intentional; risk flags are collected in section 3.
2.1 Additive (Goblin-only), 7 units
src/nostr/(11 files): payment messaging subsystem. Contacts, NIP-17 DMs, NIP-44/59 encryption, relay management, standalone identity (random or imported, never seed-derived), NIP-05 registration, message protocol/ingest, rkv store.src/nym/(3 files): Nym mixnet transport, in-process SDK (no sidecar since Build 65/66), SOCKS5 client, HTTP routing and WebSocket relay dial through the mixnet.src/gui/views/goblin/: the phone-first payment app surface (GoblinWalletView), the primary UI since the Phase-0 redesign.src/gui/theme.rs: design token system (Light/Dark/Yellow themes, density scales);colors.rsnow maps its legacy API onto these tokens.src/http/price.rs: CoinGecko fiat/BTC rate fetch routed over the Nym mixnet, lazy cached per currency (backs the Pairing setting).locales/*.yml: ~370 goblin-prefixed keys across 6 locales (drift-tested), plus new uncommitted Phase-0 keys.examples/avatar_ring.rs(uncommitted Phase-0): avatar ring rendering example.
2.2 Removed (GRIM-only), 3 units
src/tor/(4 files): Tor service, onion addresses, circuit management. Replaced bysrc/nym.src/gui/views/settings/tor.rs: Tor proxy/bridge settings UI. The settings screen block that used it is gone; an integrated node control panel took its place.src/gui/views/wallets/wallet/transport/: Tor transport panel (slatepack address over onion, QR). Replaced by the goblin payment surface and Nostr/Nym slatepack exchange.
2.3 Modified inherited files, 40 files, one line each
Core:
src/lib.rs: nostr/nym modules replace tor; BUILD number constant; rustls ring provider setup; Nym warm-up; Goblin branding, fonts, theme wiring.src/logger.rs: drops the arti (Tor) log filter.src/main.rs: adds a Wayland app_id so the taskbar icon resolves.src/gui/mod.rs: exposespub mod theme.src/gui/app.rs: Android status-bar icon heartbeat, app visibility frame mark, X11 background fill fix for light/yellow themes, "Goblin - Build N" title.src/gui/colors.rs: refactored from hard-coded constants to theme-token lookups, same API.
Platform:
src/gui/platform/mod.rs: new platform hooks: save_file, share_text, pick_image_file, set_status_bar_white_icons, vibrate_error, vibrate_copy.src/gui/platform/android/mod.rs: JNI implementations of the above (SAF save, share sheet, status-bar icon color, haptics, image picker).src/gui/platform/desktop/mod.rs: camera rework for QR scanning: enumeration off the UI thread, native device indices (v4l gaps), YUYV/NV12 to JPEG transcoding, graceful frame errors; plus pick_image_file. Deliberate Build 9 robustness fix, confirmed again.
Views, shared:
src/gui/views/mod.rs: exposespub mod goblin.src/gui/views/views.rs: Goblin mark instead of Grim logo, quiet "Build N" label, theme-driven tinting.src/gui/views/content.rs: (Phase-0, uncommitted) integrated-node warning only shows when node autostart is enabled, so external-node setups are not nagged.src/gui/views/camera.rs: "No camera found" after 5 s wait, modal_ui inlined at callers, adds a QR decode test with the center mark.src/gui/views/input/edit.rs: additive builders (hint_text, text_color, body) plus native-IME path; soft-keyboard suppression default changed fromis_android()totrueon all platforms (post-Build-39 change, intentional: native input everywhere).
Views, network and settings:
src/gui/views/network/connections.rs: adapts to the changed NodeConfig API (get_api_address returns a full address, URL built as http://address).src/gui/views/network/settings.rs: drops the "listen on all interfaces" toggle, direct radio IP selection.src/gui/views/network/setup/node.rs: uses get_api_ip_port instead of the removed combined call.src/gui/views/network/setup/p2p.rs: P2P setup reduced to port only, per-interface binding UI removed.src/gui/views/settings/content.rs: Tor block removed; integrated node controls (status, enable, autorun, link to full node settings) added.src/gui/views/settings/mod.rs: dropsmod tor.
Views, wallets:
src/gui/views/wallets/mod.rs: visibility widened (pub mod wallet) so the goblin surface can reuse wallet views; slightly broader than GRIM's pub(crate), cosmetic.src/gui/views/wallets/content.rs: transport content removed, goblin surface is the wallet screen; (Phase-0, uncommitted) back button no longer falls through to the wallet chooser, wallet switching goes through explicit switch/lock controls.src/gui/views/wallets/creation/mnemonic.rs: word_list_ui made pub(crate) for reuse.src/gui/views/wallets/wallet/mod.rs: dropsmod transport.src/gui/views/wallets/wallet/content.rs: goblin surface owns the wallet screen and modal lifecycle; GRIM's legacy_container_ui kept under#[allow(dead_code)]on purpose.src/gui/views/wallets/wallet/request/invoice.rs: GRIM's newer sender-slatepack-address input (typed plus QR scan) not adopted; Goblin's request flow rides Nostr instead.src/gui/views/wallets/wallet/request/send.rs: scanner modal UI inlined here after the camera.rs modal_ui removal.src/gui/views/wallets/wallet/txs/content.rs: SendingTor state and SendTor/FinalizeTor task buttons removed.src/gui/views/wallets/wallet/txs/tx.rs: Tor finalization states and guards removed, slate state read simplified, generic "address" label.
HTTP, node, settings, wallet core:
src/http/mod.rs: registers the price module.src/http/release.rs: update checks point at Goblin releases, build-number versioning instead of semver, goblin artifact names, platform list trimmed.src/node/config.rs: does not carry GRIM's newer IPv6/all-interfaces work (a91d901); Goblin stays IPv4 host:port with split get_api_address/get_api_ip_port. See section 3.src/node/node.rs: Android notification wording fix ("Listening" when the integrated node is off in external-node setups).src/settings/config.rs: adds theme, density, pairing (Off/Usd/Eur/Gbp/Jpy/Cny/Btc/Sats), last_wallet_id; migrates the legacy fiat_preview flag; check_updates fallback flipped to false when the key is absent (GRIM falls back to true). Intentional: no clearnet phone-home by default.src/settings/settings.rs: TorConfig removed, working dir renamed .grim to .goblin.src/wallet/config.rs: adds get_nostr_path/get_nostr_db_path storage helpers.src/wallet/connections/external.rs: default mainnet node list reordered and extended (api.grin.money first, then main.us-ea.st, grincoin.org, main.gri.mw, raubritter). See section 4, open decision.src/wallet/store.rs: rkv capacity headroom (+16) so the Nostr store can coexist with the tx store without reopen churn.src/wallet/types.rs: SendingTor action and SendTor/FinalizeTor tasks replaced by NostrSend/Request/Resend/PayRequest/DeclineRequest/CancelOutgoing/CancelSend; adds ManualSlatepackOutcome. No slate state machine change.src/wallet/wallet.rs: about +733 lines, additive only: nostr identity lifecycle, NostrService, payment-message tasks, last-fee cache; GRIM's slate/tx state machine, locking, and encryption untouched; Tor send/post paths removed. (Phase-0, uncommitted: from_unlocked_keys drops the derivation_account arg.) The garbled duplicate comment near line 333 noted in Build 39 remains, cosmetic.Cargo.toml: arti/tor dependency stack (9 crates) swapped for nostr-sdk, nym-sdk, nostr-relay-pool, reqwest(socks), tokio-socks, rustls(ring); openssl vendored on Android/Linux; grin crates come from thenode/submodule via path deps.
3. Risky or unexpected findings
Nothing looks accidental or money-dangerous in inherited code. Items worth eyes:
- IPv6/multi-interface node support (upstream-newer, not adopted). GRIM added all-interface binding, IPv6 parsing, and a listen-all toggle (node/config.rs plus the network settings UI). Goblin is IPv4 host:port only. Not a bug, but a growing gap against upstream; decide whether to pull it or declare it out of scope for a phone-first wallet.
- Invoice sender-address input (upstream-newer, not adopted). GRIM's invoice request screen can attach the requester's slatepack address (typed or QR). Goblin's request flow carries identity over Nostr instead, so this was consciously not picked up. Revisit only if manual slatepack invoicing should reach feature parity with GRIM.
- check_updates fallback flip (true in GRIM, false in Goblin when the config key is missing). Intentional privacy default, but the Default struct still writes Some(true) on first run, so the flipped fallback only matters for configs missing the key. Harmless, slightly inconsistent.
- edit.rs soft-keyboard default now differs from GRIM on desktop too (true everywhere). This is a deliberate post-Build-39 change for the native IME path; noted because the Build 39 audit recorded it as byte-identical, which is no longer true.
- Build 39 items re-confirmed: camera/MJPEG desktop rewrite is a deliberate QR fix; the X11 background fill in app.rs is a real fix; legacy_container_ui is intentionally kept dead; wallet.rs nostr layer is additive and does not touch GRIM's slate handling.
4. Open product decision
Default mainnet node order (src/wallet/connections/external.rs): Goblin ships
api.grin.money first (Build 92, health-verified), then the Goblin-run main.us-ea.st, then
grincoin.org, with GRIM's main.gri.mw demoted to fourth. Intended infra lean for the fork, still
awaiting explicit owner confirmation. This remains the single open decision from the Build 39
audit.
5. Vendored wallet audit (goblin/wallet vs GRIM's wallet pins)
Reference points:
- Goblin pin:
c2db754= tip ofardocrat/walletbranchgrim, clean checkout, zero local edits. - GRIM local checkout:
8847ee5(branch-grim-side lineage plus "build: fix deps"). File-level delta to Goblin: 20 files, all explained by the lineage split, none by Goblin edits. - GRIM recorded pin (superproject HEAD):
5c54e7c=grim-stagingtip. File-level delta to Goblin: 29 files plus staging-only.gitmodules/grin/submodule andimpls/src/adapters/tor.rs,impls/src/tor/arti.rs(arti Tor client). Goblin-side extra:impls/src/adapters/http.rs(staging folded it into the Tor adapter).
Two facts drive every verdict below:
- Goblin's app never enters the wallet's synchronous send flow: every
InitTxArgsis built with..Default::default()(send_args always None) andreceive_txis always called with r_addr None (goblin/src/wallet/wallet.rs lines ~1400-1550). Slatepack exchange happens at the app layer over Nostr/Nym, and slatepack files are written by the app (create_slatepack_message), not by the wallet API. So every grim-staging hunk that lives inside thetry_slatepack_sync_workflowcall sites is unreachable code for Goblin. - Goblin's wallet lineage already has
update_tx_slate_statewired insidelibwallet/src/api_impl/foreign.rs(lines ~131, 170, 230) with hard?propagation on the actual receive/finalize money path. Staging only calls it best-effort (let _ =) at the API layer after Tor sends.
5.1 Commit-by-commit verdicts (plan INCLUDE list vs grim-staging@5c54e7c)
| Commit | Subject | In Goblin wallet? | Verdict |
|---|---|---|---|
129ad2f |
save last-scanned block, wallet scanning (#748) | Yes, git ancestor | Already present; Goblin also carries the stronger full_scan_fix trio (2880-block window, last-block hash) staging lacks |
06ab92a |
lmdb update (#755) | Yes, git ancestor | Already present; plus 840bde7 backend migration and ff1238c/b197aff lmdb no-panic fixes on top |
9570ed4/e9e75c5 |
openssl 0.10.80 (#752) | Yes (9570ed4 ancestor) |
Already present; Cargo.lock confirms openssl 0.10.80. e9e75c5 is the same change on the shallow lineage |
602d79e/8401963 |
rust edition 2021 (#749) | Yes (602d79e + da3f60b ancestors) |
Already present; all member crates say edition 2021 |
d4867d5 |
remove panics (slatepack/slate parsing) | No | PORT. git apply --check passes clean against c2db754. Turns unwrap/panic into typed errors in slatepack armor/types and slate_versions ser/v4_bin, adds a malformed-plaintext decrypt test. Directly protects Goblin: slatepacks arrive from untrusted Nostr peers |
1825e66 |
lock on process-invoice while updating slate state | No | Hunk 1 (scope the wallet mutex tightly around owner::process_invoice_tx) is inert today because send_args is always None, but it is cheap future-proofing for a path Goblin does call (pay(), wallet.rs:1503). Hunk 2 patches an update_tx_slate_state block that only exists in the staging lineage. Port hunk 1 (optional), skip hunk 2 |
f92a2d6 |
slatepack concrete error on sync workflow, send-requirement detection | Partially, effectively | The money substance (update_tx_slate_state with real error propagation) already exists in Goblin's libwallet, stronger than staging's. The rest changes try_slatepack_sync_workflow's signature/behavior (Tor send ergonomics), TorConfig::skip_send, and CLI command flows Goblin never runs. Skip |
5c20635/86bae1c |
version bumps to 5.4.1 | No | Metadata only; Goblin wallet is 5.4.0-alpha.1. Bumping would dirty a clean submodule for zero behavior. Skip |
ca5686a |
node submodules (#758), grin submodule build wiring | No, solved differently | Staging vendors grin node crates as a grin/ submodule inside the wallet repo. Goblin's wallet branch already wires grin crates via ../../node/* path deps to the goblin/node submodule. Adopting ca5686a would move the build base, explicitly off-limits. Skip |
5.2 MIXED commits, hunk classification
3f89cbc (api: output slatepack file after tor finalization for invoice, update slate state after
tor finalization on receive):
- Money hunks:
api/src/owner.rsremoval of the double state update in init_send_tx (fixes a bug f92a2d6 introduced in the staging lineage; the block does not exist in Goblin's lineage, N/A);api/src/owner.rsoutput_slatepack_file() helper plus its call after the invoice sync send (Goblin writes slatepack files at the app layer, dead path);api/src/foreign.rsslate state update after the receive-side sync send (Goblin's libwallet receive_tx already updates state internally, dead path). - Tor hunk:
impls/src/tor/arti.rscosmetic cleanup, staging-only file. Skip. - Net: nothing to port.
2292cb3 (api: log errors on update tx slate state and slatepack file output after tor sync
flow):
- Money-adjacent observability only: converts two
let _ =calls into match + error! logging, inapi/src/foreign.rsandapi/src/owner.rs. Both call sites are the Tor sync flow and do not exist in Goblin's lineage. Port only if the 3f89cbc-equivalent code ever lands. Skip.
Plan EXCLUDE list (411bcff arti client, 4587eb9 global Tor state, 1806098 tor send check,
5c54e7c/8696288 pay_tor_result merges, all of impls/src/tor/ arti work): confirmed skipped,
nothing from these is present in or needed by Goblin. Note Goblin's wallet still contains
upstream grin-wallet's old process-based impls/src/tor/ module; it is unused by the app
(Owner::new is constructed with tor_config None) and harmless.
5.3 Port list
Mechanics: commit inside the wallet submodule on a Goblin-owned branch (or fork remote) on top of
c2db754, then bump the wallet gitlink in the goblin superproject. Do NOT rebase the submodule
onto grim-staging and do not advance grim's own submodule pointer.
- 1. DONE 2026-07-01: cherry-picked as
906dc55on new local branchgoblin-money(basec2db754), unpushed. Libwallet slatepack tests pass incl.slatepack_decrypt_rejects_malformed_plaintexts; goblin lib tests (44) pass against the patched submodule. Original item: Cherry-pickd4867d5"Removing some panics" onto goblin/walletc2db754. Applies clean (verified withgit apply --check). Files:wallet/libwallet/src/slate_versions/ser.rswallet/libwallet/src/slate_versions/v4_bin.rswallet/libwallet/src/slatepack/armor.rswallet/libwallet/src/slatepack/types.rsThen run the libwallet tests, including the newslatepack_decrypt_rejects_malformed_plaintexts.
- 2. SKIPPED 2026-07-01 (ponytail): dead path in Goblin (send_args always None, no Tor sync workflow), mutex releases at fn end; revisit only if a slatepack sync workflow is ever adopted. Original item: (Optional hardening) Port hunk 1 of
1825e66towallet/api/src/owner.rsprocess_invoice_tx: wrap thewallet_inst.lock()/lc_provider/owner::process_invoice_txsequence in an inner block so the wallet mutex drops before the send_args branch. About 6 lines, adapt to Goblin's 6-arg try_slatepack_sync_workflow context. Skip hunk 2 (targets staging-only code). - 3. Confirmed skipped, already present:
129ad2f,06ab92a,9570ed4/e9e75c5,602d79e/8401963. - 4. Confirmed skipped, Tor-only, dead-path, or build-base:
f92a2d6(except its update_tx_slate_state substance, already present),3f89cbc,2292cb3,1806098,411bcff,4587eb9,5c20635/86bae1c(version metadata),ca5686a(grin submodule wiring, superseded by Goblin's node/ path deps),5c54e7c/8696288(merges). - 5. PENDING at commit time (gitlink bump deferred per commit discipline; submodule working tree carries the patch now). After the gitlink bump:
cargo build,cargo clippy -- -D warnings, wallet unit tests, and a slatepack round-trip between two Goblin identities (malformed-slatepack input now errors instead of panicking).
6. Summary counts
- Additive: 7 units (nostr 11 files, nym 3 files, views/goblin, theme.rs, http/price.rs, locale keys, Phase-0 example).
- Modified inherited files: 40 (all intentional; zero unexplained drift).
- Removed: 3 units (src/tor 4 files, settings/tor.rs, wallet transport panel).
- Vendored crates: untouched. wallet = ardocrat/wallet@grim
c2db754exactly, node = ardocrat/nodebce5a714, both clean. - Wallet port work: 1 required cherry-pick (
d4867d5), 1 optional adapted hunk (1825e66hunk 1), everything else already present or correctly excluded.