composePaymentUri method

  1. @override
String composePaymentUri({
  1. required String recipient,
  2. required int amountZat,
  3. String? memoText,
  4. List<int>? memoBytes,
})
override

Compose a ZIP-321 payment URI (the §2.4 lossless request token) from one form leg — the form flow's bridge into propose/queueSend; a scanned-QR flow would call those with the URI directly. SYNCHRONOUS, no money movement: the adapter validates the recipient + memo against the wallet's OWN network (so a cross-network address is the SDK's typed NetworkMismatch, never a host-supplied-network footgun) and returns the URI string. The bridge crossing (encodePaymentUri) lives in the adapter — never in the UI layer — so this whole flow stays host-VM testable behind a fake. Throws a typed WalletApiError for a bad address / un-sendable memo.

FR-28 — memoBytes attaches an OPAQUE machine memo (the ZIP-302 0xFF arm) instead of memoText. The two are mutually exclusive; the SDK never interprets the bytes, and the wire zero-pads anything under 511 bytes, so a host that needs its own length frames it INSIDE them.

Implementation

@override
String composePaymentUri({
  required String recipient,
  required int amountZat,
  String? memoText,
  List<int>? memoBytes,
}) {
  composeCount++;
  lastComposeRecipient = recipient;
  lastComposeAmountZat = amountZat;
  lastComposeMemo = memoText;
  // FR-28: recorded, never interpreted — a test asserts the exact bytes the
  // form handed the encoder, which is the only place the "survives a
  // re-compose" contract can be checked without a device.
  lastComposeMemoBytes = memoBytes == null ? null : List<int>.of(memoBytes);
  if (composeThrows != null) throw composeThrows!;
  // A synthetic, deterministic URI — the host-VM fake never touches the native
  // ZIP-321 encoder; the round-trip fidelity is covered at the SDK level.
  return 'zcash:$recipient?amount=$amountZat';
}