prepare method

Future<void> prepare()

Propose the shield (the FIRST half) — DETERMINISTIC, LOCAL (no keys/proofs/ network). On a shieldable balance → ShieldReady with the confirmable numbers; on null → ShieldNothingToShield (below the threshold, honest no-op); on a typed failure → ShieldUnavailable.

Implementation

Future<void> prepare() async {
  if (_inFlight) return; // re-entrancy: ignore taps while a step runs
  final session = _requireSession();
  if (session == null) return;

  // Synchronous transient transition BEFORE the first await — the double-tap
  // interlock (a second prepare sees `_inFlight` and no-ops). NON-const: the
  // post-await guards match on INSTANCE IDENTITY (#330) — a canonicalized
  // const would alias a different build cycle's transient.
  // ignore: prefer_const_constructors
  final preparing = ShieldPreparing();
  _set(preparing);
  try {
    // The SAME honest-degradation timeout the receive-address providers use
    // (`walletFfiWedgeTimeout`): `proposeShield` is a LOCAL, no-network DB
    // read on the blocking pool, so a hang means the FFI boundary is WEDGED — not a
    // slow network. Without this the sheet would spin on "Preparing…" forever on a
    // money surface with no escape. A `TimeoutException` is not a `WalletApiError`,
    // so it routes through the catch to `ShieldUnavailable(couldNotPrepare)` (with a
    // "Try again"), exactly like the receive flow's wedged-load handling.
    final proposal = await session.proposeShield().timeout(
      walletFfiWedgeTimeout,
    );
    if (_disposed || !identical(state, preparing)) return;
    _set(
      proposal == null
          ? const ShieldNothingToShield()
          : ShieldReady(proposal),
    );
  } catch (error) {
    if (_disposed || !identical(state, preparing)) return;
    _set(classifyShieldPrepareFailure(error));
  }
}