composePaymentUri method
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';
}