walletSendReportFor function

WalletSendReport walletSendReportFor(
  1. SendFlowOutcome flow, {
  2. String? correlationId,
})

The ONE translation from a flow's terminal fact to the host's report (FR-26). Pure + total, so it is unit-tested at its boundary with no widget tree and no device.

Lives HERE, beside the other reducers, and not in the public report file: the report DTOs are the host's vocabulary and must not depend on this package's internal flow states — the dependency runs internal → public.

Three honesty rules are enforced here rather than trusted to callers:

  • The txid list, not the outcome variant, decides whether a transaction exists. An outcome that says "saved for retry" over an EMPTY result set created nothing to cite, and reporting that as a transaction hands the host a payment record it can never reconcile.
  • The motion axis is three-valued. A two-step (ZIP-320) send whose legs ALL came back unaccepted can be the core #307 already-known race — the wallet's own background pass landed them first and the funds ARE in motion — and this layer cannot tell. Saying false there invites a host to offer "pay another way" over a payment already on its way.
  • The recipient amount rides a TAGGED report only (FR-46): set when correlationId is non-null, whatever the motion — an untagged push gets nothing new.

Implementation

WalletSendReport walletSendReportFor(
  SendFlowOutcome flow, {
  String? correlationId,
}) {
  switch (flow) {
    case SendFlowNothingCreated():
      return WalletSendNoTransaction(correlationId: correlationId);
    case SendFlowIndeterminate():
      return WalletSendUnclassified(correlationId: correlationId);
    case SendFlowQueued(:final queuedSendId):
      return WalletSendQueuedOffline(
        queuedSendId: queuedSendId,
        correlationId: correlationId,
      );
    case SendFlowSent(
      :final outcome,
      :final txids,
      :final isTwoStepTex,
      :final singleRecipientZat,
    ):
      switch (outcome) {
        case SendAlreadySubmitted():
          return WalletSendAlreadySubmitted(correlationId: correlationId);
        case SendSignFailed():
          return WalletSendNoTransaction(correlationId: correlationId);
        case SendSucceeded():
        case SendSavedForRetry():
        case SendKept():
        case SendTexInMotion():
          if (txids.isEmpty) {
            return WalletSendUnclassified(correlationId: correlationId);
          }
          final broadcast = switch (outcome) {
            SendSucceeded(:final txCount) => txCount,
            SendSavedForRetry(:final broadcast) => broadcast,
            SendKept(:final broadcast) => broadcast,
            SendTexInMotion(:final broadcast) => broadcast,
            _ => 0,
          };
          return WalletSendTransactionCreated(
            txids: txids,
            broadcastCount: broadcast,
            motion: switch (outcome) {
              SendTexInMotion() => WalletSendMotion.inMotion,
              // The #307 residual: a two-step with NOTHING accepted is either
              // "nothing went out" or "the background pass already landed both".
              // Indistinguishable here, so say so.
              _ when isTwoStepTex && broadcast == 0 =>
                WalletSendMotion.indeterminate,
              _ => WalletSendMotion.notInMotion,
            },
            // FR-46: TAGGED reports only — the tag is what makes the host's
            // join honest; an untagged push gets nothing new (ADR-0558's
            // one-sided-tag rule).
            recipientAmountZat: correlationId == null
                ? null
                : singleRecipientZat,
            correlationId: correlationId,
          );
      }
  }
}