createWatchOnly method
Create a WATCH-ONLY wallet from an exported unified full viewing key
(#397 §3.7 D2 / ADR-0538 — the consumer half of exportUfvk). ufvk is
the standard uview1… / uviewtest1… string; garbage, a truncated
artifact, or a WRONG-network key throw a typed
WalletErrorKind.invalidViewingKey (a well-formed cross-network key is
WalletErrorKind.networkMismatch) BEFORE any store side effect — the
controller renders it as a fixable input fault, exactly like a bad
mnemonic word. NOT a spend-key crossing: a UFVK is viewing capability
(total history visibility, no spend, no seed), so a watch-only wallet
has NOTHING to back up and goes straight to Active (no
backup-confirmation step).
birthdayHeight is REQUIRED (unlike restore's optional creation time):
a watch-only import has no lazy seed-path, so the account is imported
eagerly at this floor. Derive it from the user's picked date via
estimateBirthdayHeight. Too-high hides older history (the same contract
as a seed restore); too-low only scans longer.
Same BOUNDEDNESS + NON-CLOBBER contract as restore. A crash-window
account-less remnant CONVERGES on a re-run with the same artifact; a
DIFFERENT artifact over an existing watch-only store throws
walletAlreadyExists.
Implementation
@override
Future<WalletSession> createWatchOnly(
String ufvk, {
required int birthdayHeight,
}) async {
createWatchOnlyCount++;
lastWatchOnlyUfvk = ufvk;
lastWatchOnlyBirthdayHeight = birthdayHeight;
if (holdCreateWatchOnly != null) await holdCreateWatchOnly!.future;
if (failCreateWatchOnly != null) throw failCreateWatchOnly!;
exists = true; // a watch-only wallet now exists on disk
(session as FakeWalletSession).isWatchOnlyResult = true;
return session;
}