WalletHandle class abstract

Constructors

WalletHandle()

Properties

hashCode → int
The hash code for this object.
no setterinherited
isDisposed → bool
Whether the underlying Arc is disposed.
no setterinherited
runtimeType → Type
A representation of the runtime type of the object.
no setterinherited

Methods

authorizeParkedSend({required PlatformInt64 id, required PlatformInt64 createdAt}) → Future<ParkedAuthorization>
AUTHORIZE a parked send NOW (FR-23-b) — sign a send the user already committed, at this user-present moment, instead of waiting for a background pass to sign it. Pass BOTH the id AND the createdAt from the `ParkedSend` the user acted on.
birthdayHeight() → Future<int?>
The account birthday height — the scan floor a rescan rebuilds from and the lowest useful rescanFrom target. Clamp a rescan date-picker's DEFAULT with this instead of a blind "one year ago": map the default date to a height with estimateBirthday and take the max of the two — on any wallet younger than a year the blind default silently costs hours of scanning. Do NOT max-clamp a date the USER picked: an explicitly earlier pick is the post-restore recovery path (this height may itself be a too-recent restore estimate, and rescan exists precisely to go below it), so clamping it up to this floor silently no-ops that recovery. null before the account is provisioned (first sync hasn't run). A height, never money. WalletErrorKind.invalidState if the handle is closed.
cancelParkedSend({required PlatformInt64 id, required PlatformInt64 createdAt}) → Future<bool>
CANCEL a parked (queued) send the host listed via listParkedSends — the user-facing escape hatch to discard a TEX send waiting for the multi-step path (which would otherwise AUTO-BROADCAST once that path is enabled — see the `ParkedSend` DOUBLE-PAY CAUTION; cancel is the SAFE counter-affordance, never a re-send). Pass BOTH the id AND the createdAt from the `ParkedSend` the user is cancelling. Returns true if it was removed, false if it was already gone / began sending (post-multi-step-enable) / its rowid was reused (idempotent). On false, re-read listParkedSends — but do NOT present it as definitively "cancelled" nor invite a re-send: a send that began sending has LEFT the parked surface, so the payment may be IN-FLIGHT (direct the user to the activity list; a re-send risks a DOUBLE-PAY). IRREVERSIBLE + UNCONFIRMED — it discards the committed intent immediately; the host MUST confirm before calling (the confirm dialog is v-4b). MONEY-SAFE: only a still-Queued intent is deleted (no note reserved, nothing signed, no tx on-chain — cancel cannot strand funds), and the createdAt identity pin makes a reused id cancel NOTHING. §5.4: ids/timestamps only — no amount or address crosses. Throws WalletErrorKind.invalidState on a closed handle.
checkOlderSwapAddresses() → Future<SwapAddressCheckReport>
#390 — "Check older swap addresses": widen the range the wallet watches so the normal sync surfaces older swap deposits/refunds a SEED-ONLY RESTORE left unchecked (a wallet with a long swap history restored from its phrase). It does NOT itself find funds — it WIDENS, then any older swap money appears in the balance over the next minutes of sync. Returns a counts-only SwapAddressCheckReport — render "checking older swap addresses as your wallet syncs; anything found appears in your balance", NEVER "found X". Rerunnable (each accepted run goes deeper).
close() → Future<void>
Close the wallet: stop and join the sync loop, QUIESCE any straggling scan-batch section (a bounded wait — v-5c finding #2), then release the single-writer lock and close storage on a background thread — all before this future resolves, so a subsequent open at the same dbDir never observes a half-closed DB. BOUNDED residual: a wedged straggler past the quiesce budget can keep the lock alive briefly after this resolves (integrity still holds; the lock frees the moment it ends — an immediate re-open should tolerate a transient WalletAlreadyOpen with a short retry). The chain is the source of truth, so closing loses nothing. Idempotent: a second call is a no-op.
currentAddress() → Future<String>
The wallet's default receive address — a Unified Address with Orchard + Sapling receivers (no transparent receiver), encoded for display/QR. Deterministic per seed.
currentTransparentAddress() → Future<String>
The wallet's transparent RECEIVE address (Recv-2 / ADR-0528): the account's canonical external-scope (m/44'/coin'/0'/0/0) P2PKH t-address, surfaced ALONGSIDE the shielded UA behind the receive screen's address-type toggle (default shielded). PUBLIC + reused-address-linkable (visible on-chain) — the host labels it as such and keeps the shielded UA the recommended default. Deterministic per seed; the spending key never leaves Rust (§4.1). Returns the bare t-address string (no librustzcash type crosses the bridge). A wallet whose stored UFVK predates transparent-inputs (pre-GA) surfaces the typed key-derivation error, never a panic.
deliveryState({required String txidHex}) → Future<DeliveryState?>
The wallet's DELIVERY OBLIGATION for one transaction it created — the same value a history row carries on TxSummary.delivery, for a caller that holds a txidHex (the one send returned in its TxSubmitResults, passed back verbatim). Read it right after a send whose result was not all-success: null or anything but DeliveryState.retryPending means the wallet is NOT promising to send it on a later sync, so do not say so. null for a transaction this wallet did not create, one that has expired (its TxStatus says so) or one it holds no row for. Reads the aux connection off the engine lock, so it never blocks sync. Throws WalletErrorKind.txidInvalid for a txidHex that is not 64 hex characters; WalletErrorKind.invalidState on a closed handle.
dialCounts() → Future<DialCounts>
How many connections each arm served, by outcome, since this wallet opened — the numbers to show beside the transport state (the network panel's "N private, M direct"). One connection is one dial; the count is exactly what the SDK's own wallet.dial log line records, so the two never disagree. In memory only: a reopen starts at zero. Readable in every lifecycle phase, cheap, no I/O. Never logged by the SDK — it crosses only on this call. WalletErrorKind.invalidState if the handle is closed.
dismissSwapRecord({required String swapId}) → Future<bool>
Remove one in-flight swap record. USER-INTENT only since #367: call from the terminal card's Done (the outcome rendered full-screen = seen) or the home row's explicit Remove — a terminal OBSERVATION pins via recordSwapOutcome instead, so an away-at-terminal outcome is never erased unseen. Display-only state: this never touches the deposit outbox, the in-flight guard, or detection (money in motion keeps moving; only the home row goes). Idempotent — returns false for an already-absent id, so a raced double-dismiss is harmless. A never-dismissed record self-lapses out of listInFlightSwaps at SwapRecord.expiresAt.
dispose() → void
Dispose the underlying Arc.
inherited
enableNearSwap({required SwapProviderConfig config, required bool swapEnabled, SwapKill? declaredKill}) → Future<void>
Construct the NEAR Intents swap provider over THIS wallet's own transport and turn the swap on-ramp ON (spec §3.5; the swap-near feature). The composition entry behind the swapQuote/swapExecute/watchSwapStatus surface — until it succeeds, WalletHandle.swap is conceptually null (kill layer 2) and those three throw SwapErrorKind.swapDisabled.
ephemeralReservationPressure() → Future<ReservationPressure>
How close the wallet is to the one-time-address (ZIP-320 ephemeral) gap-limit ceiling — a `ReservationPressure` (COUNTS-only). ⚠ outstanding is a LOWER BOUND (#315 slice-2 proof discovery): the underlying engine read is BLIND to slots held by sends CREATED then never confirmed (their output row exists from create-persist), so it can read 0 while the window is actually full — treat it as "at least N in use", never as proof the window is clear. Surface it as a proactive hint; the real gate is the engine's TexSendLimitReached at send time, and the #315 reclaim (reclaimEphemeralSlots) gates on its own wide reservation read, never on this gauge. An INDICATOR for display, NEVER a money gate. 0 outstanding in the common case (no TEX send has reserved an address). Throws WalletErrorKind.invalidState on a closed handle.
exportUfvk() → Future<String>
THE ONE SANCTIONED UFVK EGRESS (#397, spec §3.7 D1 / ADR-0538; §5.4 HARD-D re-cast: "never leaves the device SILENTLY"). Returns the wallet's full viewing key in the standard unified encoding.
isWatchOnly() → Future<bool>
Is this a WATCH-ONLY wallet (#397 §3.7 D4/D5)? SYNC + lock-free (the kind is immutable after open). The reference UI keys its chrome on this — the watch-only badge, hidden Send/Shield/Swap affordances, the no-backup security screen; the typed WalletErrorKind.watchOnly refusals stand regardless, so a host ignoring it only ever sees honest errors.
listInFlightSends() → Future<List<InFlightSend>>
The IN-FLIGHT two-step (TEX/ZIP-320) sends: first leg signed + broadcast, send not yet complete — the user's money is IN MOTION through a wallet-controlled one-time address. The host renders a DURABLE "on its way — don't send it again" cue from this list (see `InFlightSend` for the lifecycle + the DOUBLE-PAY CAUTION): the post-send result screen's caution is dismissible, and without this surface an in-flight two-step reads as an ordinary pending send exactly when a worried user is most tempted to re-pay. DISJOINT from listParkedSends (parked = queued, nothing signed yet; in-flight = signed + broadcast) — a send is in at most one. Read-only PULL, cheap + LOCAL (a SQLite read; no network, no money movement); call it on the same edges as listParkedSends (sync ticks / resume / after a send completes). Empty when nothing is mid-flight — the common case. Amount-only (§5.4; no recipient/txid/address). Throws WalletErrorKind.invalidState on a closed handle. HOST CONTRACT on a THROW: the call fails LOUD on a corrupt store row (fail-closed — a broken row must never silently hide a possibly-in-motion send); prefer SURFACING that failure over hiding the cue, because a swallowed error removes the double-pay caution for EVERY in-flight send at once (a host may fall back to hiding only because the pending tx stays visible in transactions).
listInFlightSwaps() → Future<List<SwapRecord>>
The durable IN-FLIGHT SWAP surface (W-swap-5, #366): every swap whose order was registered at execute and has neither been dismissed nor self-lapsed. THE kill→relaunch re-attach: after a process restart the home screen lists these and re-opens live tracking by feeding SwapRecord.id to watchSwapStatus — without this list an armed OutOfZec deposit was invisible everywhere while the one-swap-in-flight guard refused new swaps. NOT gated on swap being enabled or the §3.5 kill state (a local read is not swap traffic — records stay readable so the user never loses sight of money in motion). Read-only, cheap + LOCAL (a SQLite read); call it on the same edges as listParkedSends (sync ticks / resume / after an execute). Empty when nothing is in flight — the common case. §5.4: SwapRecord.id is render-never-log. Throws WalletApiError only for a closed handle / store fault.
listParkedSends() → Future<List<ParkedSend>>
EVERY queued send sitting in Queued that has NOT completed yet — a one-time-address (TEX) two-step awaiting its drain pass / ceiling-parked, or (since #331) a plain single-step send waiting in the offline queue; the DTO kind says which (presentation-only). They have NO on-chain transaction yet (INVISIBLE in the activity / transaction list), so this surfaces them "saved & pending" instead of silence — across an app relaunch it is the ONLY place the committed spend exists (hidden, a queued send is a DOUBLE-PAY window). Each is amount + id + kind + paused (`ParkedSend`); the recipient address NEVER crosses the bridge. A read-only PULL the host calls on demand; cause-agnostic copy (never "blocked"/"stuck"). A HEALTHY row auto-broadcasts once it can drain — present it per the `ParkedSend` DOUBLE-PAY CAUTION (never invite a re-send) and EARMARK its amount over the balance (it stays in spendable). A paused row does NOT auto-broadcast (#315 — the wallet gave up auto-retrying it; it waits for retryParkedSend or cancelParkedSend and MUST be phrased as paused, never "will send when ready"). To DISCARD either, pass its (id, createdAt) to cancelParkedSend (the SDK cancel — the SAFE counter-affordance, landed v-4a, kind-agnostic; the host UI button/confirm is v-4b). Throws WalletErrorKind.invalidState on a closed handle.
machineMemos({required String txidHex}) → Future<List<Uint8List>>
FR-27 — the MACHINE-MEMO bytes of one transaction, scoped to the prefixes registered on WalletConfig.machineMemoPrefixes. Returns them in output order; txidHex is the txidHex a history row gave you, passed back verbatim.
mintDiversifiedAddress() → Future<MintedDiversifiedAddress>
Mint the NEXT public diversified receive address (FR-8 / Recv-4, ADR-0537): a fresh unlinkable Unified Address for a contact/invoice that still credits the one wallet account. Payments to it are detected by the normal shielded scan — including after a seed-only restore — with no extra host work. Same receiver set as currentAddress (Orchard + Sapling, no transparent). Every call returns a NEW address from the never-recycle counter, which survives restore: each seed-only restore starts minting in a fresh RANDOM band (~2048 mints wide), so no prior life's addresses are re-issued — deterministic for lives up to ~2048 mints, ≤ ~2^-18 per-restore band-overlap chance beyond (attribution/linkage caveat, never a fund risk). Local + offline + cheap (no network, no sync requirement). Deterministic per index — keep MintedDiversifiedAddress.diversifierIndex as the durable attribution key, and treat it as WALLET-INTERNAL (never share it with a counterparty — it is a sequential mint ordinal). Throws WalletErrorKind.keyDerivation before any account is provisioned (exotic — accounts import eagerly at create), WalletErrorKind.invalidState on a closed handle. §5.4: render/store the fields, never log them.
noSuchMethod(Invocation invocation) → dynamic
Invoked when a nonexistent method or property is accessed.
inherited
probeSyncServer({required SyncServerChoice choice}) → Future<SyncServerProbe>
Dial choice under THIS wallet's Tor policy and ask the server who it is (one GetLightdInfo, 15 s budget). Refuses typed — WalletErrorKind.syncServerUnreachable (could not dial, timed out, or the server refused), WalletErrorKind.networkMismatch (the server is on another Zcash network), WalletErrorKind.syncServerNotOffered — and NEVER switches. Use it for the picker's "Check server" step; a switch probes again on its own. default probes endpointUrl.
propose({required String requestUri}) → Future<SendProposal>
Prepare a send from a ZIP-321 payment URI — propose (the FIRST half of the propose→confirm→send flow, inc-2d-ffi). Runs the audited note-selection + fee + change over the wallet DB and returns a SendProposal: the EXACT numbers (total/fee/change, per-recipient pool + amount, the §5.1 de-shield disclosure) the user confirms before signing. DETERMINISTIC — no spending keys, no proofs, no network, no DB writes; NOTHING is sent. The opaque proposal token is retained Rust-side; send consumes it by proposalId.
proposeShield() → Future<SendProposal?>
Propose shielding the wallet's detected transparent funds into its shielded pool — the Recv-3 companion to the transparent receive address (exchange withdrawals and swap-in deliveries land TRANSPARENT, §1.7; this is the privacy-positive "move them shielded" action). Wraps the audited propose_shielding (Rule Zero), gated at the 0.001-ZEC shielding threshold:
queueSend({required String requestUri}) → Future<String>
Queue a send for OFFLINE-first delivery (inc-2d-ffi). Durably persists the send INTENT (the ZIP-321 URI) and returns its opaque id IMMEDIATELY — NO network, NO signing, NO money movement. The §6.2 "queued is a normal state" path: tap send with zero connectivity and it survives a process kill, then proposes→signs→broadcasts on the next online sync pass (the resubmission machinery) — proposing at SEND time, not queue time, is exactly what stops a long-queued send from ever carrying a stale anchor.
reclaimEphemeralSlots() → Future<ReclaimOutcome>
RECLAIM a one-time-address (TEX) send window bricked by LEAKED reservations — sends that reserved a one-time address but never confirmed, which the wallet cannot free on its own (#315). Self-mints a small amount from your SHIELDED balance to the highest provably-abandoned one-time address; mining it reopens the WHOLE window at once. MONEY-MOVING + EXPLICIT: gate it behind the #327 authorizer with an honest-cost disclosure (the mint + a later recovery are TWO transactions, ~4 network fees; the minted principal returns to your wallet).
recordSwapOutcome({required String swapId, required SwapOutcome outcome}) → Future<bool>
Pin an OBSERVED terminal outcome into an in-flight swap record (#367): call when the tracking stream reports a TERMINAL status (success / refunded / failed) so the home row renders the truth even if the user was away at observation time — deleting on observation (the pre-#367 contract) erased outcomes the user never saw, durably if the process died in the gap. FIRST-WINS (a terminal is forever; a later conflicting pin no-ops) and idempotent — returns false for an absent / lapsed / already-pinned id. Display-only state; never touches the deposit outbox, the in-flight guard, or detection. §5.4: swapId is render-never-log.
recoverableEphemeralFunds() → Future<List<RecoverableEphemeralFunds>>
Funds recoverable on wallet-controlled one-time (ephemeral) transparent addresses — a TEX transfer whose forwarding step expired, or an exchange that returned a deposit to that single-use address. A read-only PULL the host calls on demand (e.g. to render a "recover" row on the balance). Each entry is amount + welded reorg-finality (`RecoverableEphemeralFunds`); the one-time address never crosses the bridge. PRESENTATION: the amount is a SUBSET of the displayed balance — render it as part of the balance, NEVER as additional funds. Live now that TEX two-step sends run in production (gate-removal, 2e-2b-v-5); empty on a wallet that has made no TEX send (no one-time address has ever held a return).
rescanFrom({int? fromHeight}) → Future<void>
Rescan from an EARLIER birthday to recover funds an over-high restore birthday skipped (ADR-0534 — the in-app fix for the ADR-0533 "balance reads zero, sync says done" gap). Rebuilds ONLY the data DB at fromHeight — NO recovery-phrase re-entry, and NO key material crosses this bridge (the only argument is a bare block height). A SEALED-KEYCHAIN wallet re-imports from the seed that stays sealed in the keychain. A HOST-CUSTODIED (SeedPersistence.none) wallet opened through openWithHostSeed re-imports through its registered seed port at the FIRST sync after the rescan — the same pull its first-ever sync made, native-to-native, so the host must have the seed STAGED for that sync (until it runs, the rebuilt wallet honestly reads "account not yet provisioned", balance zero, history empty); a port answering the WRONG seed is refused WalletErrorKind.seedMismatch before a note is derived, and the next sync asks again. A host-custodied wallet with NO seed port registered is a typed up-front WalletErrorKind.seedRequired (nothing can serve the re-import). Pass null to scan the FULL history (floors to Sapling activation, a slower but money-SAFE scan, NEVER ~tip — except a freshly-generated wallet, whose durable creation stamp re-floors null at ~creation: money-equivalent and hours faster; pass an explicit height to go deeper). Pair with estimateBirthday for a date-picker UX.
retryParkedSend({required PlatformInt64 id, required PlatformInt64 createdAt}) → Future<bool>
RETRY a paused parked send the host listed via listParkedSends — resume the SAME queued intent after the wallet gave up auto-retrying it (see `ParkedSend::paused` (crate::api::state::ParkedSend)). The wallet caps auto-retries of a one-time-address send because every retry permanently uses up one of a small number of address slots; when the user believes conditions changed (back online, the recipient service reachable again), THIS is the resume path — NOT cancel + re-enter, which re-pays the retry budget on a fresh send and MUST NOT be offered while the paused row exists (double-pay + cap evasion). Pass BOTH the id AND the createdAt from the `ParkedSend`. Returns true if the send was re-armed (it attempts again on the next background pass — not instantly); false if it was already gone / began sending / its rowid was reused / it had nothing to reset — an untouched (never-retried) row is refused, never silently refilled (idempotent — re-read listParkedSends). MONEY-SAFE: this only re-arms the existing queued intent's retry budget — nothing is signed or sent by the call itself. §5.4: ids/timestamps only. Throws WalletErrorKind.invalidState on a closed handle.
revealMnemonic() → Future<List<String>>
Reveal this wallet's BIP39 recovery phrase — word by word, in index order — so the user can back it up. The ONE sanctioned outbound key-material crossing (spec §3.3). The words ARE the wallet's whole secret: show them ONCE on a secure screen the host marks FLAG_SECURE, never log / persist / screenshot / transmit them, and prompt the user to write them down offline. Returns the words as a Dart List<String> (24 for a generated wallet; a restored phrase reveals its own length) — Dart memory cannot be zeroized (the documented §10 exposure); the Rust side holds the words in zeroizing buffers and wipes its own copies when this call returns.
send({required PlatformInt64 proposalId}) → Future<List<TxSubmitResult>>
Sign + broadcast a prepared proposal — the SECOND half of the send flow (inc-2d-ffi). proposalId is the opaque one-shot token from the SendProposal propose returned; pass back ONLY that id — the host cannot mutate what gets signed (the retained proposal is the sole source of truth, so a tampered id is just a registry miss, never a wrong-amount sign). The transient spending key is derived, used to sign inside a blocking proving task, and zeroized — it NEVER crosses this bridge. Each resulting tx is PERSISTED before broadcast (§6.3), then broadcast over a FRESH per-tx Tor circuit (§2.3) after a §5.3 jitter.
snapshot() → Future<WalletState>
The on-resume cold snapshot (spec §3.3): everything a UI needs to re-render after a background gap — balance, sync status, Tor state, chain tip, and a monotonic seq that also stamps every live stream item (so a stale stream event can never beat a fresher snapshot in a UI race: keep the larger seq). Cheap; safe to call on resume before re-subscribing to the live streams.
startSync() → Future<void>
Start the background sync loop — required for the live watchSyncStatus stream (and balance progress) to advance; a freshly opened wallet does NOT auto-sync. The engine runs over the configured endpoint/Tor with retry/backoff + a stuck-sync watchdog, so a fault surfaces as a SyncStatus.stalled EVENT on the stream, never a thrown error or a dead stream. Idempotent (a second call while running is a no-op).
stopSync() → Future<void>
Stop the background sync loop and await clean teardown. Idempotent; durable progress is preserved (the chain is the source of truth), so stopping loses nothing. Prompt even mid-sync (cooperative cancel ≤ one batch scan). Use it to pause sync WITHOUT closing the wallet; close stops sync as part of its own teardown. A no-op on an already-closed handle (nothing to stop).
swapAddressCheckCoverage() → Future<SwapAddressCoverage>
#390 — the render-only coverage read for the "Check older swap addresses" sheet: how far the wallet has already checked, for the coverage line and the Check/Check-deeper affordance. COUNTS ONLY — no address or index crosses. Read-only, side-effect-free; safe to poll while the sheet is open. Throws WalletErrorKind.invalidState on a closed handle.
swapExecute({required SwapQuote quote}) → Future<String>
Execute a quote — register the swap intent with the provider and (for OutOfZec) queue the §4.4 ZEC deposit send (spec §2.6/§3.3). Pass back the SwapQuote swapQuote returned; this is LEAST-AUTHORITY-equivalent to the send token even though it carries the whole DTO: the SDK PEEKS its own durable record by quote.id, compares the DTO's terms and binding to it field-by-field — a difference is SwapErrorKind.quoteTermsDiffer with nothing consumed — then claims the record single-flight; EVERY execution term comes from that record, never this DTO (ADR-0555), so a tampered depositAddress/amount cannot redirect funds. Returns the SDK-minted SwapId as an opaque String (quote.id itself, never the provider's deposit address) — do NOT parse it; pass it to watchSwapStatus as-is. Idempotent against a double-tap / crash-retry: ONE deposit per quote, ever (§5.4: nothing logged).
swapListTokens() → Future<SwapTokenList>
The dynamic source-asset list for the IntoZec picker (spec §3.3b D5/L6) — LAZY: the host calls this when the user opens the IntoZec form (NOT on launch, so an idle user emits no token traffic). The SDK fetches the provider's /v0/tokens on its OWN circuit (never the sync circuit), filters to real quotable foreign assets (the ZEC asset itself + any $0/null-price entry dropped), and returns the picker rows + a freshness flag. On a fetch fault it serves the LAST good list with SwapTokenList.fresh = false (the host shows a "couldn't refresh, showing cached" banner — honest degradation), never a blank picker; an empty fresh list is the honest "no assets available right now." The host maps each (chain, symbol) to a BUNDLED icon (never CDN-fetched). No token field is logged (§5.4).
swapQuote({required QuoteRequest request}) → Future<SwapQuote>
Request a bounds-checked swap quote (spec §2.6/§3.3) — the FIRST step of the on-ramp. request names the direction, the user's EXACT side (the anchor every returned number is bounds-checked against before anything is signed), the slippage tolerance, and the direction-specific destination/refund fields. The returned SwapQuote carries the provider deposit address, the min-out floor, the ZEC side in zatoshis, and the §2.6 privacy disclosure the host MUST render — funds NEVER move here. No amount/address is logged (§5.4).
sweepEphemeralFunds() → Future<EphemeralSweepSummary>
MANUALLY recover funds stranded on wallet-controlled one-time (ephemeral) transparent addresses into the wallet's own SHIELDED balance — the user-discoverable recovery for the funds recoverableEphemeralFunds surfaces, PLUS late exchange returns / a 2nd deposit / aggregate dust the automatic surface cannot see (it raw-enumerates each one-time address's on-chain UTXOs). MONEY-MOVING: it signs + broadcasts one consolidating transaction per funded address (each over its own circuit, never co-spent — so a recovery never links the one-time-address set on-chain).
switchSyncServer({required SyncServerChoice choice}) → Future<void>
Probe, then switch the wallet onto choice and REMEMBER it (the row survives restarts and rescans; default forgets it). The session is swapped in place: the sync loop is stopped and joined, the choice written, the wallet rebuilt over the SAME database — no rescan, no re-download; funds, history, queued sends and the sync verdict are untouched. Afterwards call startSync, and re-subscribe the live streams the swap ends — the old session's watchSyncStatus stream ENDS (the reference UI swaps its whole session, as after a rescan). watchTorState and watchIncomingFunds are the exceptions: their channels are CARRIED into the rebuilt session, so those subscriptions keep delivering and start reporting the NEW session at once. A transport chip rendered from watchTorState must not go silent here, because the rebuilt session is exactly where the private path can change. The swap service is re-initialised: call enableNearSwap again if you use it.
syncFor({required int budgetMs}) → Future<BoundedSync>
Sync for at most budgetMs milliseconds, then report how far the wallet got — for a host that holds the process only briefly (a background wake, a pull-to-refresh). ONE pass runs; when the budget runs out it stops the way stopSync stops the loop (progress kept, nothing half-written) and the call returns a report with finished: false, never an error. The budget is always yours: the SDK has no default and schedules nothing.
syncServers() → Future<List<SyncServer>>
The sync servers this host OFFERS (WalletConfig.syncServers, as validated) — WITHOUT their keys: authValue is always null on this list (the host supplied it and holds it); authHeader says whether an entry is gated. The picker, P3-13. WalletErrorKind.invalidState if the handle is closed.
syncServerStatus() → Future<SyncServerStatus>
Which server the wallet dials, which remembered choice produced it, and whether a fallback is in force. Render the HOST of effectiveUrl on the sync sheet's Server row — the connection's own truth, so the display and the dial can never drift. Readable in every lifecycle phase. WalletErrorKind.invalidState if the handle is closed.
toString() → String
A string representation of this object.
inherited
transactions({required int limit, String? after}) → Future<HistoryPage>
The wallet's transaction history (FR-1 — the activity-history list), newest first and paginated by an OPAQUE keyset cursor. Pending (unmined) rows sort ABOVE confirmed ones; after = None is the first page, after = Some(token) the page after the cursor (the `HistoryPage::next_cursor` from the previous call — passed back VERBATIM; never parse it); limit caps the page. The keyset cursor (full (height, tx_index, txid) sort key) means a page boundary never drops a same-height row nor strands the confirmed history behind a full page of pending txs. Each row is a typed TxSummary (txid hex in block-explorer order, SIGNED net amount, status, fee, memo flag) — no key material, amounts stay typed. Reads librustzcash's audited v_transactions view off the engine lock, so it never blocks sync.
watchIncomingFunds({String? sinceCursor}) → Stream<IncomingFundsEvent>
Stream incoming-funds events (ADR-0536 — the FR-1 "funds arrived" hook): ONE replay event first (arrivals already in history strictly above sinceCursor; a null cursor gets a count-0 baseline that hands you a cursor to persist), then a live event per scan batch that detected arrivals, and a memoRefresh nudge when a later enhancement pass changes how existing rows read — either decrypted tx data landed, or a transaction's chain status did. Treat memoRefresh as "re-pull", never as "new memo data exists".
watchSwapStatus({required String swapId}) → Stream<SwapStatus>
The live SwapStatus stream for one in-flight swap (spec §3.3/§7) — the swap parallel of watchSyncStatus. swapId is the opaque id swapExecute returned. The stream emits the CURRENT status immediately, then polls at a bounded exponential cadence (5→60s; §7), coalescing onto each change. It NEVER completes on a transient provider fault (a stall is retried, never a dead stream) — it completes only on a TERMINAL status (success/refunded/failed), when the host cancels the subscription, or when the wallet is closed/swap is hard-killed (§3.5).
watchSyncStatus() → Stream<SyncStatus>
The live SyncStatus stream (spec §3.3) — the host's honest sync indicator. The stream emits the CURRENT status immediately on subscribe (so re-subscribing after an AppLifecycleState.paused gap re-renders at once), then every change coalesced latest-wins (a burst never floods the UI). It NEVER completes on a transient fault — a stall is a SyncStatus.stalled EVENT, not an error or EOF; it completes only when the host cancels the subscription or the wallet is closed (a final status is drained first). Call startSync for it to advance past idle.
watchTorState() → Stream<TorState>
The live TorState stream — the transport state as it changes, so a host renders the private path's truth without polling snapshot. The stream emits the CURRENT state immediately on subscribe, then every change coalesced latest-wins: the moment a preferred wallet leaves the private path (TorState.fellBack — the switch a user should be told about), and the moment a ready path has carried nothing for a minute (TorState.unanswered). It NEVER completes on a transient fault; it completes only when the host cancels the subscription, the wallet is closed, or rescanFrom rebuilds the session (which ends every live stream — re-subscribe after a rescan). A switchSyncServer does NOT end it: the stream is carried across the swap and reports the new session. Readable in every lifecycle phase, like snapshot's tor.

Operators

operator ==(Object other) → bool
The equality operator.
inherited

Static Methods

createGenerated({required WalletConfig config}) → Future<WalletHandle>
Create a BRAND-NEW wallet — a fresh 24-word seed is generated in Rust (OsRng), sealed per config.seedPersistence, and used entirely Rust-side; the seed NEVER crosses this bridge. The wallet is then opened and returned ready to sync.
createWatchOnly({required WalletConfig config, required String ufvk, required int birthdayHeight}) → Future<WalletHandle>
Create a WATCH-ONLY wallet from an exported viewing key (#397, spec §3.7 D2 / ADR-0538 — the consumer half of exportUfvk). Full read path (sync, balance, history, receive addresses); every spend-class surface — send, shield, swap, revealMnemonic, rescan — throws the STRUCTURAL WalletErrorKind.watchOnly (nothing to retry: this wallet holds no spending keys, by design). Reopen later with the plain open; a pre-#397 binary refuses the store typed instead of half-opening it.
createWithHostSeed({required WalletConfig config, bool? freshlyGenerated}) → Future<WalletHandle>
Provision a NEW host-custodied-seed wallet at config.dbDir (FR-15), pulling the seed from the C-ABI seed port the HOST registered (in its own native library) via zec_wallet_register_seed_port. The seed crosses native-to-native from the host lib into this one and NEVER through Dart — use this INSTEAD of createGenerated/restore for the keys-in-host integration (Relim ADR-0031): there is no mnemonic and no Dart-side seed. The SDK PINS config.seedPersistence to none (a host-custodied seed is never sealed at rest) — any other value is enforced to none, not an error.
custodyDisclosure({required WalletConfig config}) → Future<CustodyDisclosure>
The PRODUCTION custody disclosure (FR-14 H1) for the wallet at config.dbDir — render an HONEST pre-wipe confirmation (a hardware-held key deleted vs best-effort removal) and an ambient custody badge from it. An ACTIVE probe of the measured vault tier; reads NO seed and unseals nothing. A Self-less static so it works before/independent of an open handle. VaultAbsent (headless desktop) ⇒ tier: "none", eraseAssurance: bestEffort.
open({required WalletConfig config}) → Future<WalletHandle>
Open an EXISTING wallet at config.dbDir.
openWithHostSeed({required WalletConfig config}) → Future<WalletHandle>
Open an EXISTING host-custodied-seed wallet at config.dbDir (FR-15), attaching the registered C-ABI seed port so signing + fresh-refund-address derivation can pull the seed on demand. For a wallet whose account is ALREADY imported (the steady state), balance, history, and the receive address need NO seed and never call the port; the host stages narrowly and clears per the FR-12 contract (take-once for a send). This only transports the pull native-to-native, never through Dart.
restore({required WalletConfig config, required List<String> mnemonicWords, String? passphrase}) → Future<WalletHandle>
Restore an existing wallet from its BIP39 recovery phrase — the ONE sanctioned INBOUND key-material crossing (spec §3.3; the outbound counterpart is revealMnemonic). mnemonicWords are the recovery words in order (a List<String>, the symmetric counterpart to revealMnemonic — never a single joined string in Dart). Pass each word LOWERCASED (and trimmed): the audited BIP39 validator does NOT case-fold, so a soft keyboard's autocapitalized first word would otherwise reject a CORRECT backup. A mis-cased/unknown word is a typed WalletErrorKind.invalidMnemonic carrying its INDEX (never silently a different wallet); inter-word and surrounding whitespace IS tolerated. They are BIP39-validated (checksum + word list) and turned into a sealed seed ENTIRELY Rust-side, then the wallet is provisioned at config.dbDir and opened. The words live in Dart memory only as long as the restore screen holds them — Dart memory cannot be zeroized (the documented §10 inbound exposure, identical framing to the outbound revealMnemonic). Once Rust owns the words (as a SeedSource), the seed + phrase ride zeroizing buffers and wipe on drop; the brief inbound word-list copy itself is un-wiped (FRB cannot marshal a zeroizing type) — the same minimal, documented residue as the outbound reveal, minimised not eliminated.
severCustody({required WalletConfig config, required int deadlineMs}) → Future<SeverReport>
The duress force-sever (FR-53): make the wallet at config.dbDir unrecoverable within deadlineMs, EVEN WHILE something still holds it open (a handle never closed, a straggler past close, a rescan or server switch mid-rebuild, an open in flight, another process) — the case where wipe refuses with WalletErrorKind.walletOpen.
walletExists({required WalletConfig config}) → Future<bool>
Cheap probe: is a COMPLETED, openable wallet provisioned at config.dbDir? Drives the host's boot fork — false → offer create, true → open it (then gate on backup confirmation). It is LOCK-FREE and does NOT open the wallet: it reads only the on-disk provisioning marker, so it never contends the single-writer lock the subsequent open needs (an open-and-catch-notFound would, and then break that open).
wipe({required WalletConfig config}) → Future<void>
Delete the wallet at config.dbDir (FR-14) — the host's "delete wallet" / panic-wipe primitive. Deletes the keychain wrap key FIRST (so on any crash point this key store can no longer open the on-disk seals or the DB ciphertext; how strong that is is custodyDisclosure's eraseAssurance, ADR-0571), then removes the data directory. The seed NEVER crosses this bridge — only the config does.
wipeForce({required WalletConfig config}) → Future<void>
Force-complete a wipe whose keychain custody item is ALREADY GONE — the escape hatch for the rare states where a plain wipe returns WalletErrorKind.keystoreInconsistent (a manually-deleted item, a pre-namespacing bare item, an iOS ThisDeviceOnly item that didn't migrate while dbDir did). It SKIPS the verify-real-sever guard, so the caller MUST be certain config matches the wallet's create/open config — a wrong config under force would delete the wrong files. Use ONLY after a plain wipe returned keystoreInconsistent on a wallet you are sure is yours.