walletReceiveAddressReadProvider top-level property

FutureProviderFamily<String, Object?> walletReceiveAddressReadProvider
final

The wallet's current receive address (the unified address for account 0) — what the user shares to receive ZEC. Only meaningful when a session exists (the surface is reachable only from a provisioned wallet). NOT key material — the address is public by design; §5.4 NEVER-LOG still applies (display/copy, never log).

An identity-fenced read/view PAIR since #385 (the #381 (c) idiom — see the fence note above), for two coupled reasons:

  • Per-identity CACHE, now BY CONSTRUCTION (E2E-1): the account-0 address is STABLE for the session (it does not rotate without a re-provision), so the derive runs ONCE per wallet identity and every later screen entry / tab toggle renders from the view's held value. The pre-#385 plain provider already cached per container in-package (the review proved the repeat NOT reproducible here at HEAD), so the device-observed re-derive-per-visit likely rode host/session churn — which this shape handles identically (same identity ⇒ held value; a REAL identity flip ⇒ honest fresh derive, exactly right for duress). The device symptom itself is re-verified on the #340 pass. No resume invalidation either way (a background gap can't stale a stable address).
  • The fence is LOAD-BEARING now: with a held value in play, an un-fenced provider would carry the OWNER's address onto a duress/decoy identity's receive surface (copyWithPrevious retention). The family re-key drops the old identity's element — same guarantee as the four money views.

Invalidate the READ to force a fresh derive (the screen's retry does, via the view's manual-invalidation forwarding); watch the VIEW. Both readers PIN riverpod's retry OFF (walletNoSilentRetry) and are ANCHORED for the container's life from the wallet screen (#386 — see the anchor note at _WalletActive): together those are what make the timeout contract below actually hold on a phone.

Implementation

final walletReceiveAddressReadProvider = FutureProvider.autoDispose
    .family<String, Object?>((ref, identity) {
      // Dying-element guard — see walletSnapshotReadProvider. An error (not a
      // value): the screen renders its not-set-up state and never watches this
      // with a null session, so the arm is defensive-only, and the error state
      // it would paint is recoverable via the screen's retry. The two causes
      // carry DISTINCT messages (#386 — a superseded-identity flush read is
      // expected traffic; "no wallet session" in a log for it was a misdirect).
      if (ref.watch(walletIdentityProvider) != identity) {
        return Future<String>.error(
          StateError(
            'walletReceiveAddressReadProvider read under a superseded '
            'wallet identity',
          ),
        );
      }
      final session = ref.watch(walletSessionProvider);
      if (session == null) {
        return Future<String>.error(
          StateError(
            'walletReceiveAddressReadProvider read with no wallet session',
          ),
        );
      }
      // Honest degradation (principle 6): currentAddress() is a LOCAL
      // derivation, so a hang means the FFI boundary is wedged or starved —
      // not a slow network. Bound it so a wedged call surfaces the screen's
      // error state ("try again") instead of an infinite spinner on a money
      // surface.
      return session.currentAddress().timeout(walletAddressDeriveTimeout);
    }, retry: walletNoSilentRetry);