walletReceiveAddressReadProvider top-level property
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
(
copyWithPreviousretention). 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);