check method
Run ONE deep scan. Returns the counts-only report, or null when nothing
ran — a scan is already in flight (that run owns the outcome) or the session
is gone. A typed refusal / fault PROPAGATES (the caller maps the copy); the
latch resets and the coverage read re-pulls on EVERY exit so the sheet
settles.
Implementation
Future<SwapAddressCheckReport?> check() async {
if (state) return null; // single-flight: the running scan owns the outcome
final session = ref.read(walletSessionProvider);
if (session == null) return null;
state = true;
try {
// Bound the widen FFI call (rel-MED2): a wedged bridge must degrade to a
// reset latch + an honest "couldn't start", never a permanent "Checking…"
// that ALSO pins the core aux mutex the widen serializes on. The widen is
// a durable local aux write — a timeout after a slow commit self-corrects
// on the coverage re-pull in the finally below. Same package-wide wedge
// bound every other local FFI read carries.
final report = await session.checkOlderSwapAddresses().timeout(
walletFfiWedgeTimeout,
);
// A widen executed — arm the C1 progress cue (survives closing the sheet).
_armProgress(session);
return report;
} on TimeoutException {
// The FFI call exceeded the wedge bound, but the widen is a durable local
// aux write that MAY have committed (rel-H1). Arm the cue anyway: the
// banner only shows if the coverage re-pull below confirms a pending band,
// so a genuinely-failed widen stays hidden — while a slow-but-committed
// one keeps its "still surfacing" cue instead of a false "nothing changed".
_armProgress(session);
rethrow;
} finally {
// Guarded: the controller has no provider dependencies (session is
// `ref.read`), so only a whole-scope teardown can unmount it mid-scan —
// and an unmounted-ref throw in `finally` would REPLACE the scan's real
// outcome. Re-pull the coverage read so the sheet's line advances.
if (ref.mounted) {
state = false;
ref.invalidate(swapAddressCoverageReadProvider);
}
}
}