features/wallet/send/send_state
library
Classes
-
RecipientEmpty
-
Field is blank — the neutral first state (no message, no premature warning).
-
RecipientInvalid
-
The address doesn't parse for this network — not sendable.
-
RecipientShielded
-
A shielded recipient: PRIVATE and memo-capable ("Shielded · private").
-
RecipientStatus
-
The LIVE classification of the recipient field — the §5.1 privacy axis
(shielded/private vs transparent/public) plus the validity the form gates the
Review action on. A sealed family so the screen renders each state
exhaustively (no default blind spot). Computed SYNCHRONOUSLY from
WalletSession.validateRecipient (local, no network — feedback even fully
offline) on every keystroke; carries NO money state (design invariant 1).
-
RecipientTransparent
-
A transparent / transparent-only recipient: PUBLIC on-chain and
memo-incapable ("Transparent · public"). Honest and allowed — the §5.1
de-shield disclosure warns at Review; the send is never blocked for it.
-
RecipientWrongNetwork
-
Well-formed but for a DIFFERENT network (e.g. a testnet address pasted into a
mainnet wallet) — a distinct, renderable state (not "malformed").
-
SendAlreadySubmitted
-
The one-shot token was already consumed (a double-tap / re-entry). The notes
are NEVER broadcast twice (§6.3) — honest "already submitted", not an error.
-
SendAmountFault
-
Host-side amount parse failure (before any bridge call) — see ZecAmountFault.
-
SendCategoricalFault
-
A categorical fault with no payload — rendered from reason.
-
SendFlowIndeterminate
-
The flow ENTERED the spend and then lost the answer — the sign+broadcast ran
and something threw after it (a host authorizer's own bookkeeping, an SDK
error raised past the point of no return). Money MAY have moved and this
layer cannot tell.
-
SendFlowNothingCreated
-
The flow ended with NOTHING signed and nothing queued — structurally, not by
inference: the spend closure was never entered.
-
SendFlowOutcome
-
What became of ONE send flow, addressed by flowId — the authoritative
answer, published by SendController and consumed by whoever owns that
flow's host report.
-
SendFlowQueued
-
The flow durably queued an intent offline. No transaction, no txid — but the
core's queuedSendId IS the join key onto the parked-send surfaces.
-
SendFlowSent
-
The flow reached a
send. outcome is the honest reduction; txids are the
ids it minted (see sendTxids); isTwoStepTex is the proposal's shape
signal, carried because the reducer's own two-step residual cannot be
re-derived downstream (core #307). singleRecipientZat is the SENT
proposal's one-recipient total (FR-46) — copied from the proposal whose id
went to send, never a re-proposed or displayed one; null for two
recipients.
-
SendForm
-
The editable send form. fault is an inline, honest message from a failed
prepare/queue (a bad address, not enough funds, …) —
null on first entry.
-
SendFormFault
-
A FIXABLE form fault surfaced inline so the user corrects the input on the
SAME form (never a full-screen failure that discards what they typed). Reads
only the typed
WalletApiError.kind (or a host-side parse fault) — never an
error payload — so nothing sensitive (address/amount) leaks (§5.4).
-
SendInsufficientFunds
-
Not enough spendable value. Carries the audited note-selector's figures (all
zatoshis) so the host can say "you have X spendable, need Z, Y still
arriving". Never logged (§5.4) — the static
message is.
-
SendKept
-
The wallet KEPT the signed payment but has not promised to send it on its
own: for some transaction that did not go out, the core's per-transaction
delivery reading was not
DeliveryState.retryPending — a held state, no
state at all, or a reading that could not be taken. Distinct from
SendSavedForRetry on purpose (stage S8 obligation, row 10): "saved —
we'll finish sending" is said ONLY when the core reports that it will retry;
this arm says "saved" and points at Activity, where the live delivery state
is rendered. Money-safe either way — the bytes are persisted.
-
SendOutcome
-
The honest result of a
send — a broadcast failure is NOT a lost payment
(each tx is persisted and re-sent by the resubmission machinery), so the
variants distinguish "went out", "saved for retry", "already done", and
"couldn't build" rather than success/error.
-
SendOutcomeUnknown
-
The flow ENTERED its spend and then lost the answer (S7 U1): the closure
ran — a transaction may be signed and broadcast, or an intent durably
queued — and something past that point threw or declined (the host's own
authorizer code, an untyped throw). Neither "sent" nor "nothing was sent"
is true, so the screen says neither: it points at where the truth is
(Activity for a send, the pending payments for a queue) and offers no retry.
Deliberately carries NO error — nothing here renders a throw's text.
-
SendOverCeiling
-
The amount exceeds the HOST's send ceiling (
walletSendCeilingZatProvider
— e.g. an alpha roll-out cap). Carries the ceiling so the inline copy can
state the limit; refused BEFORE any compose/propose bridge call. Distinct
from ZecAmountFault.outOfRange (the protocol supply bound): this is host
POLICY, and the copy must say so honestly rather than imply an invalid
amount.
-
SendPreparing
-
propose in flight — deterministic + local (note-selection/fee), no network.
Transient; a spinner.
-
SendQueued
-
Terminal result of an offline
queueSend — the intent is durably stored and
will send on the next online sync.
-
SendQueuing
-
queueSend in flight (durably persist the intent). Transient; a spinner.
-
SendRequestExpired
-
The host's request this flow was opened with can no longer spend (stage S8
deadline, R05): the entry's mount grace ran out before a send screen
appeared, the host was told "no transaction", and that answer is final for
the request. Nothing to pay, nothing to retry — the screen states the fact
and the user starts again from the host.
-
SendReview
-
The confirm screen: show the EXACT numbers + the §5.1 de-shield disclosure,
then SendController.confirm signs and broadcasts. Holds the display
proposal (its opaque token is consumed by id on confirm) and the recipient
the user typed, echoed back so they verify WHO they're paying.
-
SendSavedForRetry
-
Some (or none) of the txs reached the network. Each is PERSISTED and will be
re-broadcast on the next sync — money-safe, the user already confirmed the
numbers. The honest "saved, will finish sending when you're back online".
-
SendSent
-
Terminal result of a
send — see SendOutcome for the honest variants.
-
SendServerSilentFault
-
The server will not say which network it is on and the wallet's grace for
such a server has run out —
RW-SYNC-003, GRACE-1 (§4p). Carries the SDK's
reason (blocks / the device clock / never confirmed) and, for the blocks
sentence, the count it names. Its OWN class, not a SendFaultReason: the
copy needs the payload, and it must never fold into
SendFaultReason.networkUpgradeUnsupported — nothing was upgraded, an
update fixes nothing, and the next step is "switch servers" (or "check the
device's date and time"). Never logged; the static message is.
-
SendSignFailed
-
Build/sign failed — no money moved. "Couldn't complete this payment; nothing
was sent." The user re-proposes (the consumed token can't be re-sent).
-
SendState
-
The send flow's states (inc-2d-ui), a sealed family so the screen renders each
phase with an exhaustive
switch (no default blind spot). The flow is
form → (propose) → review → (send) → result, with an offline-first queue
branch. Rendering layer ONLY — every transition runs through
SendController; no money state is stored here (design invariant 1; Rust is
the single source of truth). The DTOs it carries (SendProposal) are display
projections — the opaque proposal token stays Rust-side.
-
SendSubmitting
-
send in flight (sign in a blocking proving task + persist + broadcast).
Transient; a spinner. Holds the proposal so a UI can keep showing the figures.
-
SendSucceeded
-
Every tx broadcast successfully.
-
SendTexInMotion
-
A two-step TEX (ZIP-320) send where SOME but not all legs were accepted —
typically tx0 (the unshield to a wallet-controlled one-time address) out but
the forwarding tail (tx1) not; since core #307 also the reverse shape, where
tx0 read "already known" (the wallet's own background re-broadcast raced the
send and landed it first) and tx1 was accepted — either way the endpoint knows
tx0. The funds are IN MOTION on a one-time address the wallet controls —
money has LEFT the shielded pool but NOT confirmed at the recipient. This is
NOT an ordinary "each tx independently saved-for-retry" partial: the two legs
are SEQUENCED (tx1 spends tx0's output, so it can only mine after tx0). The honest
terminal copy says ONLY the permanently-true things — "on its way" + "don't
send it again" + "recover from your wallet if it doesn't complete" — and MUST
NOT promise auto-completion: tx1 can expire (~40 blocks, the common mobile
case) into a strand that only the shipped recovery (the wallet-screen
recover-now / sweep) resolves (spec §3.2i-2 UX-honesty (i); the durable
parked/recoverable surface owns the later in-motion→stranded truth and
re-notifies). Distinguished from SendSavedForRetry purely by
SendProposal.isTwoStepTex (the SSOT shape signal, 2e-2b-v-5a) — only the
interactive SEND path produces it; shield (t→z) and move-to-transparent
(z→own-t) have no TEX destination, so they never do.
Enums
-
SendFaultReason
-
The payload-free form-fault categories (the honest message axis; no codes).
Functions
-
classifyProposeFailure(Object error)
→ SendFormFault
-
Map a
propose/encodePaymentUri failure to a SendFormFault. Reads the
typed WalletApiError.kind ONLY (never a payload — §5.4); a non-FRB error is
the generic SendFaultReason.couldNotPrepare. Pure + total, so it is
unit-tested at its boundary without a device.
-
classifyQueueFailure(Object error)
→ SendFormFault
-
Map a
queueSend failure to a SendFormFault. Same kind-only discipline.
queuedSendsFull is the one queue-specific kind; everything else mirrors
classifyProposeFailure's categorical mapping (compose-time errors are
identical, since queue composes the same URI).
-
classifyRecipient(WalletSession session, String raw)
→ RecipientStatus
-
Classify the recipient field's current text against the wallet's own network.
PURE over the (sync, local) WalletSession.validateRecipient seam — reads
only
memoCapable or the typed WalletApiError.kind (NEVER an address
payload — §5.4), so it is unit-tested at its boundary behind a fake. A blank
field is RecipientEmpty (no bridge call); any non-FRB error degrades to
RecipientInvalid (honest, never a raw code — invariant 6).
-
classifySendFailure(Object error)
→ SendState
-
Map a
send (sign+broadcast) failure to the next SendState. Distinct from
the form mappers: a send failure is terminal-ish, so it routes to either the
result screen (already-submitted / sign-failed) or back to the form
(stale ⇒ re-propose, busy ⇒ retry). Reads kind only (§5.4).
-
deliveryStatesFor(WalletSession session, List<TxSubmitResult> results)
→ Future<Map<String, DeliveryState?>>
-
The core's delivery reading for every transaction in
results that did NOT
come back accepted, keyed by txid — the summarizeSendOutcome input that
makes "saved for retry" a reported fact (stage S8 obligation, row 10)
rather than a fallback. A read that fails typed is recorded as null: the
reducer then lands on SendKept (saved, no promise) — under-promising is
the honest direction when the wallet's answer could not be taken.
-
landedDeliveryStates(WalletSession session, List<TxSubmitResult> results)
→ Future<Map<String, DeliveryState?>>
-
deliveryStatesFor for a spend whose results already LANDED (R13 §4.2):
any throw — not only a typed one — reads as "no delivery state", so the
landed outcome still shows ("saved", no promise), never "outcome unknown".
Unknown means only that the answer was lost; here it was not.
-
parkedAuthorizeErrorPrecedesPersistence(Object error)
→ bool
-
The parked "Send now" twin, for
authorizeParkedSend
(Wallet::try_sign_queued_intent → drain_multi: create — the wallet-db
commit — then mark_sent_multi, then read_raw_tx) (R13 §4.1).
-
queueErrorPrecedesPersistence(Object error)
→ bool
-
The queue twin of sendErrorPrecedesPersistence, for
queueSend
(Wallet::queue_send_uri → queue_send → intent_store::enqueue):
WalletBusy and WatchOnly are the entry gates, and QueuedSendsFull is
the cap checked inside the enqueue transaction BEFORE its insert. Every
SQLite-classified kind is left out: this layer cannot tell a fault before
the insert from one at the commit.
-
sendErrorPrecedesPersistence(Object error)
→ bool
-
Whether
error, thrown by the SDK's own send AFTER the spend closure was
entered, is a kind the core raises only BEFORE any transaction is persisted
(S7 U1 fold) — so classifySendFailure's "nothing was sent" / back-to-form
routing is still the truth. Anything else lands on SendOutcomeUnknown.
-
sendTxids(List<TxSubmitResult> results)
→ List<String>
-
The transaction ids in
results, in order — every arm that HAS one, not
only the accepted ones. A tx that got no verdict (grpcFailure), was
rejected from the mempool (submitFailure), or was never attempted still
EXISTS: it is signed, persisted, and re-broadcast by the resubmission
machinery, so a host must be able to cite it. The forward-compat
TxSubmitResult.unknown arm carries no id and contributes none — which is
why a caller checks for emptiness rather than assuming one id per result.
Pure + total.
-
singleStepSendErrorPrecedesPersistence(Object error)
→ bool
-
The SINGLE-STEP twin of sendErrorPrecedesPersistence, for the shield and
move-to-transparent
send (R13 §4.1). Same path (Wallet::send_by_id →
sign_proposal_upstream → create_signed_core → broadcast_persisted),
but never a two-step: a shield is its own arm and a move pays the wallet's
own t-address, never a TEX — so mark_sent_multi is unreachable, and the
engine writes the transaction ONCE, in one transaction, on full success.
-
summarizeSendOutcome(List<TxSubmitResult> results, {required bool isTwoStepTex, required Map<String, DeliveryState?> delivery})
→ SendOutcome
-
Reduce the per-tx
TxSubmitResult list to an honest SendOutcome. A tx is
"out" only on TxSubmitResult_Success; every other arm (grpc/submit failure,
not-attempted) means persisted-but-unsent, and whether the wallet will
retry it is the CORE's to say, not this reducer's to assume.
-
walletSendReportFor(SendFlowOutcome flow, {String? correlationId})
→ WalletSendReport
-
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.