createWatchOnly method

  1. @override
Future<WalletSession> createWatchOnly(
  1. String ufvk, {
  2. required int birthdayHeight,
})
override

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;
}