feat(cashu): C1b — persist escrow-mode overrides and surface the backend in About - #234
Conversation
…end in About Closes the C1 phase of docs/cashu/README.md. Behaviour on a Lightning node is unchanged: with no `escrow_mode` tag the resolution stays Unknown, every Cashu path stays shut, and the About screen renders exactly what it did before. Rust - `Storage` gains a generic k/v accessor (`get_setting`/`set_setting`/ `delete_setting`) over the `settings` table that already existed. SQLite implements it for real and the two named active-node accessors become thin wrappers over it; IndexedDB follows its existing stub contract, so on web the overrides apply for the session only (tracked in #233). - `escrow_mode` now stores what the node advertised and the overrides separately, and resolves on read. Flipping an override therefore takes effect immediately instead of waiting for the next relay fetch, and there is no second copy of the answer that can go stale. A node switch clears the tags but keeps the overrides — forcing Cashu is a statement about this build, not about a node. Every mutator emits on a broadcast channel. - New `api/escrow.rs`: `get_escrow_mode`, `set_escrow_mode_override`, `set_cashu_mint_url_override`, `rehydrate_escrow_overrides` and an `on_escrow_mode_changed` stream. Mint URLs are validated as http(s) with a host, on write and again on rehydrate. `EscrowModeInfo.mode` is a stable marker; Dart maps it to a localized string. Dart - `MostroInstance.fromTags` parses `escrow_mode` and the three `cashu_*` tags, tri-state and parameter-gated exactly like `BondPolicy`. - About reports what the node advertises, so the backends are mutually exclusive on screen: a Cashu node gets a "Cashu escrow" section instead of the Lightning Network one. The mint renders in full rather than through the copyable row, whose 20-character abbreviation would hide the host. - `escrowModeProvider` / `isCashuAvailableProvider` expose the resolution that gates behaviour; the override controls live in a `kDebugMode`-only settings card that shows the effective resolution next to the toggle. - Strings translated in all five locales. Tests - Rust: k/v round-trip, override re-resolution without a fetch, node switch keeps the override, every mutator notifies, mint-URL rejection leaves the previous value in place. - Dart: tag parsing incl. gating and malformed values; widget tests asserting no trace of Cashu on a Lightning or silent node, and no Lightning section on a Cashu one.
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (15)
🚧 Files skipped from review as they are similar to previous changes (15)
WalkthroughThe PR adds Cashu escrow-mode parsing and About-screen support, separates advertised state from developer overrides, persists overrides through the settings store, exposes Rust state changes to Flutter, adds debug controls, and localizes the new UI across five languages. ChangesCashu escrow mode
Estimated code review effort: 4 (Complex) | ~60 minutes Possibly related issues
Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Review — C1b (strict pass)
Scope reviewed: 🔴 Major1. Read-modify-write race on the overrides — Both setters do the same thing: escrow_mode::set_overrides(EscrowOverrides {
mode,
..escrow_mode::get_overrides() // ← read
}); // ← write
Fix: give 2. final info = ref.watch(escrowModeProvider).valueOrNull;
if (info != null) {
_syncMintField(info.mintUrlOverride); // assigns _mintController.text
}Assigning Fix: move it to a listener — ref.listen(escrowModeProvider, (_, next) {
final stored = next.valueOrNull?.mintUrlOverride;
if (stored != _syncedMintOverride) { ... }
});🟡 Minor3.
4. The broadcast payload is computed and thrown away —
5.
🔵 Nit6. Inconsistent stubs — ✅ What is right
Test gaps
|
… write Addresses the strict review on #234. Major - `set_escrow_mode_override` / `set_cashu_mint_url_override` read the overrides and wrote them back as a whole struct, so two writes from the same screen interleaved and the second silently discarded the first. Both now go through `escrow_mode::update_overrides`, which mutates under one write lock. Covered by `setting_one_override_never_clobbers_the_other`. - The dev card assigned `TextEditingController.text` during `build()`, which notifies listeners in the middle of laying the field out and also wiped in-progress typing on any unrelated escrow event. Seeding now happens in a post-frame callback and updates in `ref.listen`. Minor - `is_overridden` was true whenever the override was on, including when the node genuinely advertises Cashu — so the dev card labelled a real Cashu node as forced, which is the confusion the flag exists to prevent. Now `forcing && from_tags != Cashu`. - The broadcast carried a `ResolvedEscrowMode` that the stream discarded and rebuilt, resolving twice per change. The channel is now `Sender<()>`: a bare wake-up, with subscribers reading the globals as before. - Every mutator emitted unconditionally, so a node whose capability fetch keeps failing produced a stream of identical "still unknown" events. Each now compares before/after and only notifies on a real change. - `indexeddb::delete_setting` returned Ok silently while its siblings warned. A silent no-op in a storage layer reads like working storage in a log. Tests - `overrides_survive_a_restart_through_the_settings_store` — the persist and rehydrate halves end to end against a real store, which nothing covered. - `a_change_that_changes_nothing_does_not_wake_subscribers`. - `forcing_cashu_on_a_cashu_node_is_not_flagged_as_an_override`.
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (3)
rust/src/mostro/escrow_mode.rs (1)
284-301: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueLog line fires on every capability fetch, including the no-change case.
The
changedguard suppresses the notification but not theinfo!, so a flaky relay that re-fetches the same event logs identical lines indefinitely. Moving it into thechangedbranch keeps the log a record of actual transitions.♻️ Proposed refactor
pub fn set_from_tags(mode: EscrowMode, config: CashuNodeConfig) { - log::info!( - "[escrow-mode] active node advertises {} (mint={:?})", - mode.as_marker(), - config.mint_url, - ); let changed = { let mut guard = TAGS.write().unwrap_or_else(|e| e.into_inner()); - let next = Some((mode, config)); + let next = Some((mode, config.clone())); let changed = *guard != next; *guard = next; changed }; // A re-fetch that confirms what we already knew is the common case on a // reconnect; emitting for it would wake every listener for nothing. if changed { + log::info!( + "[escrow-mode] active node advertises {} (mint={:?})", + mode.as_marker(), + config.mint_url, + ); notify(); } }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@rust/src/mostro/escrow_mode.rs` around lines 284 - 301, Move the escrow-mode info log into the existing `if changed` branch alongside `notify()`, so it runs only when the stored capability changes. Keep the TAGS update and unchanged-fetch suppression behavior intact.rust/src/api/escrow.rs (2)
23-37: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win
snapshot()resolves the globals three times, so the fields it promises are consistent can still disagree.
get_resolved(),get_overrides()andis_cashu_mode()each take the locks independently, andis_cashu_mode()re-resolves from scratch. A concurrentupdate_overridesbetween them yields e.g.mode == "lightning"withis_cashu_available == true— exactly the inconsistency the stream comment at Line 172 says cannot happen. Deriving every field from oneresolvedvalue removes the window.♻️ Proposed refactor
fn snapshot() -> EscrowModeInfo { let resolved = escrow_mode::get_resolved(); let overrides = escrow_mode::get_overrides(); EscrowModeInfo { mode: resolved.mode.as_marker().to_string(), mint_url: resolved.config.mint_url.clone(), escrow_locktime_days: resolved.config.escrow_locktime_days, settlement_margin_days: resolved.config.settlement_margin_days, is_overridden: resolved.is_overridden, - is_cashu_available: escrow_mode::is_cashu_mode(), + is_cashu_available: resolved.mode.is_cashu() && resolved.config.is_usable(), force_cashu_override: matches!(overrides.mode, EscrowModeOverride::ForceCashu), mint_url_override: overrides.mint_url, } }An accessor in
escrow_modereturning(ResolvedEscrowMode, EscrowOverrides)under one pass would close the remainingget_overrides()gap too.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@rust/src/api/escrow.rs` around lines 23 - 37, Update snapshot() to obtain the resolved mode and overrides through a single escrow_mode accessor/pass, rather than independently calling get_resolved(), get_overrides(), and is_cashu_mode(). Derive is_cashu_available and all other fields from that one consistent resolved value, while retaining the existing override fields from the same snapshot.
199-207: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winTwo independent mutexes guard the same process globals, so these tests can race the
escrow_modetests.
escrow_lock()uses a privateLOCKhere, whilerust/src/mostro/escrow_mode.rs(Line 411) serializes its own tests on a separateGLOBALmutex. Both modules compile into the same test binary and mutateTAGS/OVERRIDES, so a test from each module can interleave — theclear()in one resetting state the other just arranged. Expose one shared test lock (e.g. apub(crate)helper inescrow_mode) and use it from both.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@rust/src/api/escrow.rs` around lines 199 - 207, Replace the private LOCK used by escrow_lock() with the shared test mutex exposed by escrow_mode, and make the existing GLOBAL lock in escrow_mode accessible through a pub(crate) helper. Ensure both escrow.rs and escrow_mode tests acquire that same lock before clearing or modifying TAGS and OVERRIDES, while preserving the current state reset behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/cashu/README.md`:
- Around line 369-375: Update the documentation around the About-screen contract
and its “Done when” criteria to state that client-side escrow overrides affect
only effective/debug state, not the About section. Ensure About remains
determined solely by the node’s advertised capabilities, with Cashu and
Lightning sections rendered according to the existing advertisement logic.
In `@lib/features/about/models/mostro_instance.dart`:
- Around line 241-245: Update parseEscrowMode so it uses get('escrow_mode') and
checks for null before trimming the value; return EscrowMode.unknown only when
the tag is missing, while blank and other unrecognized values resolve to
EscrowMode.lightning. Add a regression test covering a present blank
escrow_mode.
In `@lib/features/settings/widgets/escrow_mode_dev_card.dart`:
- Around line 141-147: Replace the hard-coded hintText in the escrow mode
development card with a generated AppLocalizations accessor, add the mint URL
hint string to the supported ARB localization files, and run flutter gen-l10n to
regenerate accessors. Keep the existing settingsCashuMintOverrideLabel and
settingsCashuMintOverrideApply usage unchanged.
- Around line 87-91: Update the post-frame callback in the initialization block
guarded by _seeded and info so it reads the latest provider value when the
callback executes instead of capturing the stale seed from the current build;
preserve the existing _syncMintField behavior. Add a widget regression test
covering an escrow update delivered before the first post-frame callback and
verify the newer override remains applied.
In `@lib/l10n/app_localizations_fr.dart`:
- Around line 2317-2323: Update the source localization entry for aboutDaysValue
so zero maps to the plural “jours” while exactly one remains singular, then
regenerate localizations with flutter gen-l10n to propagate the corrected plural
behavior across generated files.
---
Nitpick comments:
In `@rust/src/api/escrow.rs`:
- Around line 23-37: Update snapshot() to obtain the resolved mode and overrides
through a single escrow_mode accessor/pass, rather than independently calling
get_resolved(), get_overrides(), and is_cashu_mode(). Derive is_cashu_available
and all other fields from that one consistent resolved value, while retaining
the existing override fields from the same snapshot.
- Around line 199-207: Replace the private LOCK used by escrow_lock() with the
shared test mutex exposed by escrow_mode, and make the existing GLOBAL lock in
escrow_mode accessible through a pub(crate) helper. Ensure both escrow.rs and
escrow_mode tests acquire that same lock before clearing or modifying TAGS and
OVERRIDES, while preserving the current state reset behavior.
In `@rust/src/mostro/escrow_mode.rs`:
- Around line 284-301: Move the escrow-mode info log into the existing `if
changed` branch alongside `notify()`, so it runs only when the stored capability
changes. Keep the TAGS update and unchanged-fetch suppression behavior intact.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 431f6111-9570-44c3-a260-bc6779248de9
📒 Files selected for processing (29)
docs/cashu/README.mdlib/features/about/models/mostro_instance.dartlib/features/about/screens/about_screen.dartlib/features/settings/providers/escrow_mode_provider.dartlib/features/settings/screens/settings_screen.dartlib/features/settings/widgets/escrow_mode_dev_card.dartlib/l10n/app_de.arblib/l10n/app_en.arblib/l10n/app_es.arblib/l10n/app_fr.arblib/l10n/app_it.arblib/l10n/app_localizations.dartlib/l10n/app_localizations_de.dartlib/l10n/app_localizations_en.dartlib/l10n/app_localizations_es.dartlib/l10n/app_localizations_fr.dartlib/l10n/app_localizations_it.dartlib/main.dartrust/src/api/escrow.rsrust/src/api/mod.rsrust/src/api/nostr.rsrust/src/api/types.rsrust/src/db/indexeddb.rsrust/src/db/mod.rsrust/src/db/sqlite.rsrust/src/frb_generated.rsrust/src/mostro/escrow_mode.rstest/features/about/models/mostro_instance_test.darttest/features/about/screens/about_screen_test.dart
| decoration: InputDecoration( | ||
| labelText: l10n.settingsCashuMintOverrideLabel, | ||
| hintText: 'http://localhost:3338', | ||
| suffixIcon: IconButton( | ||
| icon: const Icon(Icons.check), | ||
| tooltip: l10n.settingsCashuMintOverrideApply, | ||
| onPressed: info == null ? null : _applyMintUrl, |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Localize the visible mint URL hint.
Line 143 is user-facing copy. Define it in the supported ARB files, use the generated localization accessor, then run flutter gen-l10n.
Proposed fix
- hintText: 'http://localhost:3338',
+ hintText: l10n.settingsCashuMintOverrideHint,As per coding guidelines, “Localize all user-facing strings through ARB files and access them with AppLocalizations.of(context) instead of hard-coded literals.”
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@lib/features/settings/widgets/escrow_mode_dev_card.dart` around lines 141 -
147, Replace the hard-coded hintText in the escrow mode development card with a
generated AppLocalizations accessor, add the mint URL hint string to the
supported ARB localization files, and run flutter gen-l10n to regenerate
accessors. Keep the existing settingsCashuMintOverrideLabel and
settingsCashuMintOverrideApply usage unchanged.
Source: Coding guidelines
… doc CodeRabbit raised these against #234, where the files live. They had been fixed one branch too far down the stack (on the C5 branch) and so never reached this PR; applied here instead, and the stack will carry them forward. - `MostroInstance.parseEscrowMode` used `getOptional`, so a *present but blank* `escrow_mode` tag read as `unknown` while Rust's `parse_tags` reads the same event as `Lightning`. Two parsers, one event, different answers — About and the Cashu gate could disagree about the same daemon. Only an absent (or value-less) tag is unknown now. - The dev card's post-frame seed captured `mintUrlOverride` during build. An override arriving in that gap is applied by `ref.listen` first, and the captured copy then overwrote it with the older value. The callback reads the current provider value instead. - The C1 "Done when" still said flipping the override flips "the About section", contradicting the bullet directly above it. About reports what the node advertised; the override moves the resolved mode and the dev card's effective state, nothing else. Two more from the same pass, not raised inline but the same class: - `api::escrow::snapshot` derived `mode` from one `get_resolved()` and `is_cashu_available` from `is_cashu_mode()`, which reads the globals again; a node switch between the two produced a snapshot whose mode and gate disagreed. `ResolvedEscrowMode::is_cashu_usable()` expresses the gate against a value the caller already holds. - `set_from_tags` logged unconditionally while only notifying on a change, so a reconnect that confirmed what we already knew still wrote an info line. Tests: blank / whitespace / value-less / absent `escrow_mode` pinned against the Rust behaviour, and a widget suite for the dev card covering seeding, a newer override winning, and in-progress typing surviving a no-op event.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@test/features/settings/widgets/escrow_mode_dev_card_test.dart`:
- Around line 65-80: Update the test “the newest override wins over an earlier
one” to register a post-frame callback before the widget’s seed callback, emit
the newer override synchronously before the frame runs, then pump twice. Ensure
the timing exercises the first post-frame race so the assertion verifies the
latest value rather than merely testing a second pumpAndSettle cycle.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 0914a954-915b-4751-9fa8-80b8a82914b4
📒 Files selected for processing (7)
docs/cashu/README.mdlib/features/about/models/mostro_instance.dartlib/features/settings/widgets/escrow_mode_dev_card.dartrust/src/api/escrow.rsrust/src/mostro/escrow_mode.rstest/features/about/models/mostro_instance_test.darttest/features/settings/widgets/escrow_mode_dev_card_test.dart
🚧 Files skipped from review as they are similar to previous changes (6)
- lib/features/settings/widgets/escrow_mode_dev_card.dart
- test/features/about/models/mostro_instance_test.dart
- docs/cashu/README.md
- lib/features/about/models/mostro_instance.dart
- rust/src/mostro/escrow_mode.rs
- rust/src/api/escrow.rs
CodeRabbit's second pass on #234 pointed out that the regression test added in the previous commit did not reproduce what it claimed to: it emitted the newer override after the seed callback had already run, so it passed against the buggy code too. That matches what I had flagged — the interleaving looked unreachable from a widget test. It is not, with two changes: - a **synchronous** broadcast controller, so the provider update and its listener land inside the frame rather than in a later microtask; - a post-frame callback registered **before** the widget's, which emits the newer value while the widget's seed callback is still queued behind it. Verified to discriminate: with the build-time capture restored, the test fails with `Actual: 'http://old.example'` — the exact stale value. With the fix it passes.
codaMW
left a comment
There was a problem hiding this comment.
Reviewed C1b independently, full pass over escrow_mode.rs, api/escrow.rs,
db/*, the About rendering, and the dev card, on top of your strict round and
the CodeRabbit rounds. Approving.
Both Majors verified fixed:
- Override race, both setters now go through
update_overrides(impl FnOnce)
under a single write lock; no read-then-write window. - Controller-during-build
_syncMintFieldmoved out ofbuild()into
ref.listen.
Money-path safety (the part that must not move), fail-safe by construction:
- No
escrow_modetag ->Unknown-> About keeps the Lightning section it always
showed. - The gate is
is_cashu_usable(mode Cashu and a non-empty mint), not
mode == cashua Cashu node with no usable mint opens nothing. - About branches on
node.escrowMode(node-advertised), never the client
override; the widget test asserts the absence of every Cashu string on a
legacy/silent node, not just the presence of the right ones. validate_mint_urlrequires http(s)+host on write and drops non-conforming
stored values on rehydrate fails closed.- The
Err/Ok(None)fetch arms bothclear()to fail closed on an unreachable
node, with PoW deliberately left alone the reasoning in those comments is
the right call.
Your earlier minors all confirmed addressed: #3 (is_overridden no longer
conflates forced with genuinely-Cashu), #4 (broadcast is now Sender<()>), #5
(clear() only notifies when the cached state actually changed), #6 (both
IndexedDB stubs warn).
One heads-up (non-blocking): the 3 escrow_mode_dev_card_test cases fail on
my local Flutter 3.44 with a ListTile background color or ink splashes may be invisible assertion the ListTile is inside a DecoratedBox with a bg color.
CI passes them on the pinned 3.38.2, so it's a version-strictness gap today, but
that pattern will start failing once FLUTTER_VERSION is bumped. Wrapping that
ListTile in its own Material (or dropping the intermediate DecoratedBox
background) now would future-proof it. Flagging so it isn't a surprise later.
cargo test 149 passed / clippy -D warnings clean / cargo check --target wasm32-unknown-unknown clean / flutter analyze clean. The
resolve-on-read design and the honest About contract (report what the node
advertises, never what the override forces) are the right calls.
Only conflict was the generated rust/src/frb_generated.rs — resolved by regenerating the bindings from the merged sources with the pinned codegen (2.11.1) instead of hand-merging generated code.
8286c36
Closes the C1 phase of
docs/cashu/README.md. C1a (#230) added the tri-statedetection and the
is_cashu_mode()gate; this half adds persistence, the bridgesurface, and the About screen.
Zero behaviour change on Lightning. With no
escrow_modetag — every daemonin the wild today — the resolution stays
Unknown, every Cashu path stays shut,and About renders exactly what it rendered before this PR.
Rust
Settings k/v store.
Storagegainsget_setting/set_setting/delete_settingover thesettingstable that already existed in the schema.SQLite implements it for real, and the two named active-node accessors become
thin wrappers over it rather than a second copy of the SQL. IndexedDB follows
its existing stub contract (warn + no-op), so on web the overrides apply for the
session only — the whole IndexedDB backend is a stub, now tracked in #233.
Resolution on read.
mostro/escrow_mode.rspreviously cached the resolvedvalue. It now keeps what the node advertised and the developer overrides in
separate globals and runs
resolve()on read. Two consequences worth thechange:
relay fetch, and there is no second copy of the answer that can go stale;
statement about this build, not about a particular node.
Every mutator emits on a broadcast channel, so "changed" and "notified" cannot
drift apart.
api/escrow.rs(new):get_escrow_mode,set_escrow_mode_override,set_cashu_mint_url_override,rehydrate_escrow_overrides, and anon_escrow_mode_changedstream. Mint URLs must behttp(s)with a host —validated on write and again on rehydrate, so a stored value that no longer
passes tightened rules is dropped rather than silently used.
EscrowModeInfo.modeis a stable marker (unknown/lightning/cashu);Rust does not translate.
The gate stays
is_cashu_available, notmode == "cashu": a node can advertiseCashu and publish no usable mint, and that combination must not open anything.
Dart
MostroInstance.fromTagsparsesescrow_modeand the threecashu_*tags,tri-state and parameter-gated exactly like
BondPolicy— a Lightning nodecarrying a stale
cashu_mint_urlnever surfaces it as live.exclusive on screen: a Cashu node gets a "Cashu escrow" section (mint,
locktime, settlement margin) instead of the Lightning Network section. A
Cashu node that published no mint says "Not advertised" rather than rendering
an empty section. The mint renders through the plain info row, not the
copyable one, whose 20-character abbreviation would hide the host — the one
part of a mint URL that matters.
a
kDebugMode-only settings card, which shows the effective resolution nextto the toggle, so a tester is never fooled about which is which.
escrowModeProvider/isCashuAvailableProviderexpose the resolution thatgates behaviour, for the phases stacked on top of this one.
Tests
Rust — k/v round-trip and key independence; override re-resolves without a
fetch; a node switch keeps the override; every mutator notifies subscribers; a
rejected mint URL leaves the previous value in place; forcing Cashu without a
mint does not open the gate.
Dart — tag parsing including gating, case/whitespace, unrecognised backends
and malformed day counts; widget tests asserting no trace of Cashu on a
Lightning or silent node (including one carrying stale cashu tags), and no
Lightning section on a Cashu one.
Verification
./scripts/frb-generate.shwas run after therust/src/api/change.Test plan
main, no Cashu stringsanywhere, trades behave as before.
Cashu escrow" flips the effective mode instantly, with no relay round trip.
previous value survives.
not.
Follow-ups: #233 (IndexedDB persistence on web). Next in the plan is the
cdkspike, before C2.
Summary by CodeRabbit
Manual verification
Two things a reviewer needs to be sure of: nothing changed for a Lightning
user, and the override does what it claims without leaking into About.
No public daemon runs Cashu yet, so the Cashu-side rendering is covered by the
widget tests rather than by hand.
Already run on this branch
A · Lightning regression — the part that must not move
Run against the default node (
flutter run -d linux, or your usual device).main:lnd_*values;Cashu, a mint URL, a locktime or a settlement margin;
byte-identical to
main.touches the order or trade flow; if anything behaves differently, that is a
bug in this PR.
[escrow-mode] active node advertises unknownat most once per node,not once per reconnect.
B · The developer card
The card is
kDebugMode-only, so it only exists in a debug build.(developer)" is there, below "Mostro node".
flutter build linux --release(or your platform) andopen Settings → the card is absent. This is the check that matters most
— it is the one thing
flutter testcannot verify, becausekDebugModeistrue under the test harness.
C · The override behaves
In the debug build, on the developer card:
immediately — no relay round trip, no app restart. This is the whole point
of resolving on read.
"Cashu cannot run without a mint" line. That is the gate refusing to open,
which is correct.
Network section. About reports what the node advertised; the override is a
client-side statement and must never change it. If About flips, that is the
regression this contract exists to prevent.
not a urlin Mint URL override and apply → a localized error, andthe previous value is still in the field.
http://localhost:3338and apply → accepted, and "Effective mint"updates.
set. (Native only; on web they last the session — Web: IndexedDB storage backend is a stub — nothing persists across a reload #233.)
(it is a statement about this build, not about a node), while the node's own
advertised mode is re-fetched.
What is not covered by hand
The Cashu-mode About rendering — the "Cashu escrow" section replacing the
Lightning one — has no daemon to produce it yet. It is covered by
test/features/about/screens/about_screen_test.dart, which asserts bothdirections: no Cashu string on a Lightning or silent node, and no Lightning
section on a Cashu one.