Payjoin Fixes (Requires review) - #2499
Conversation
The transaction reference validation checked the whole 'txid:vout' string against the 64-hex-char txid rule, so it rejected every valid input/output/publicKey label reference. Validate the txid slice only.
Validate a label before inserting it, and skip (rather than throw on) a corrupt persisted row when reading back, so one bad row can't take down the whole label fetch.
Building a plain (local) DateTime from a UTC end date's wall-clock fields shifted the inclusive-day boundary by the device's UTC offset, so the same export included or excluded edge transactions depending on the machine's timezone. Round up in UTC when the input is UTC.
Adds is_aborted to payjoin_receivers/payjoin_senders and is_payjoin_enabled / payjoin_min_amount_sat / payjoin_expire_after_sec to settings, migrated in schema_13_to_14 with backfilled defaults (disabled, 10000 sat, 24h). PayjoinConstants gains the bounds and the documented 24h-expiry rationale. The bulk of the diff is the generated v14 schema snapshot; review the migration step, the table definitions, the seeds and the constants.
Threads the three new payjoin settings from the settings entity through the repository and datasource, fed by the DB columns added in the v14 migration.
Adds PayjoinStatus.aborted (a fallback broadcast: the payment landed as a plain transaction) and the isAborted model field, mapped to/from the new DB column and round-tripped in fromReceiverTable/fromSenderTable (fixing a pre-existing bug where isExpired/isCompleted were never mapped back, silently resetting a re-fetched session's status). The CSV export gains the matching aborted case so its status switch stays exhaustive. Entity getters logRef and canManuallyBroadcastOriginal are introduced here as the shared vocabulary the core engine (next) builds on.
Adds the payjoin settings screen (auto-saving toggle, min-amount and expiry with bounds-validating use-cases) reachable from bitcoin settings, wired through the settings cubit and locator. Includes all new payjoin l10n keys (settings + the send/receive/transactions keys the later feature PRs consume) so every stacked branch compiles standalone.
Adds a payjoinEnabledChangeStream to SettingsRepository (mirrors the existing currencyChangeStream pattern), emitted from setPayjoinEnabled, plus a WatchPayjoinEnabledChangesUsecase. Lets a live listener (the receive flow) react to the global setting changing without needing to leave and re-enter its screen.
Adds ServiceStatus.disabled (intentionally turned off, distinct from unknown/not-checked) and returns it for the payjoin service the moment the setting is off, instead of probing the OHTTP relay and painting the whole status page red for a feature the user isn't relying on. The status page shows it muted-grey with a "Disabled" label, and it no longer counts against allServicesOnline.
WatchPayjoinUsecase now emits both PayjoinReceiver and PayjoinSender (scoped by ids); the send flow needs sender completion events a receiver-only filter would swallow. ReceiveBloc keeps its receiver-only behaviour by filtering the concrete type at its own call site.
Adds a sealed WalletFailure family and a single-transaction lookup use-case returning Result<WalletTransaction?, WalletTransactionLookupFailure> (Ok(null) = synced-and-absent, Err = infra failure), per the #1895 Result migration. Consumed by the payjoin engine and transaction details.
PdkPayjoinDatasource gains stopPolling(id) and an idempotent dispose() so a resolved session's directory poll can be cut (no more stale expiry events) and tests get a clean teardown. The local datasource filters include isAborted and fetchByTxId matches originalTxId too, and sender poll logs/exceptions use a hashed logRef instead of the raw BIP21 URI.
The core of the fix. A unified backoff-poll engine arms two per-session watchers — one for the real payjoin tx, one for the original (fallback) tx — so a session always reaches a terminal state (completed or aborted) instead of hanging. tryBroadcastOriginalTransaction is split into a guarded public entry (refuses via canManuallyBroadcastOriginal, emits on the stream) and the internal mechanism; expiry/proposal processing re-fetch the fresh row and bail on terminal; resume moves out of the constructor to resumePayjoinsOnStartup, called from AppLocator once wallet/labels deps are registered. Wallet repos are injected as lazy closures; the directory receiveTimeout is raised to 35s (above the 30s long-poll hold). Adds the PAYJOIN --> LABELS edge to FEATURES.md.
…2246) SendCubit._watchPayjoin now owns resolving the flow: a completed/aborted sender session moves to success with the on-chain txid, syncs the wallet and stores the user label on the final txid; an expired session returns to confirm with a broadcast-failure error and clears the provisional txId/payjoinSender so a retry starts clean. Payjoin is attempted only when willAttemptPayjoin holds (global setting on, locally-signing wallet, non-self BIP21 with a pj= param), fail-closed by default.
The bitcoin confirm review shows a 'payjoin will be attempted' card; the success screen says when a send fell back to a plain transaction and navigates to the broadcast tx by txid (never the sender BIP21 URI); the coordinating screen shows a fallback countdown, gated on canManuallyBroadcastOriginal and only when the fallback is imminent (<=1h), and localizes its title. Adds the SEND --> TX_HISTORY edge.
Gates payjoin session creation on the global setting and carries payjoinMinAmountSat in state (with isPayjoinBelowMinimum); drops the per-address payjoin toggle in favour of that setting. Adds isPayjoinFlowOwningNavigation so the shell's generic tx-received listener defers to the payjoin screen, and lets the in-progress screen be entered on any post-started status. Guards the manual original-tx broadcast behind canManuallyBroadcastOriginal, adds isClosed guards to the watchers, splits the new-address error like the start path, and fixes the lightning in-progress navigation to use the route name. Adds the RECEIVE --> TX_HISTORY edge.
Rewrites the payjoin-in-progress screen to distinguish below-minimum, generic fallback, real payjoin and expired outcomes with their own copy, auto-navigates to transaction details on a real completion, and shows a fallback countdown (imminence-gated) plus a View Details / receive normally action driven by canManuallyBroadcastOriginal.
White in both light and dark mode, mirroring onError next to error. Lets a green success surface use theme-correct white text instead of reaching for a mismatched onSecondary/onError token.
Adds an optional action widget slot alongside the existing onAction/actionIcon plain-icon-button trailing, for a screen that needs something richer than a single icon (e.g. the receive screen's payjoin toggle chip). Positioned like the plain icon button — no extra offset stacked on top of its own padding — so it stays aligned with the title.
Adds a green/red toggle chip (ReceivePayjoinToggleButton) to the receive TopBar, wired to the global setting — only for a Bitcoin receive with a payjoin-capable wallet (funded, locally-signing), so it never shows on Liquid/Lightning or non-eligible wallets. Tapping flips the setting; long-pressing opens the payjoin settings screen. The BlocProvider now wraps the whole ReceiveScaffold (not just its body) so the TopBar can host it, gated behind a narrow selector so switching receive networks still does not rebuild the scaffold. ReceiveBloc now watches payjoinEnabledChangeStream live, creating or clearing the session the moment the setting changes anywhere in the app — no need to leave and re-enter the receive screen. Session creation (and the toggle itself) is also gated on the wallet having a confirmed balance (isPayjoinToggleable/_isPayjoinEligible): a payjoin proposal needs at least one UTXO, so there is nothing to toggle for an empty wallet, and isPayjoinLoading resolves immediately instead of waiting forever for a session that will never be created. Also suppresses the pj= endpoint from the QR (without tearing down the session) when the requested amount is below the configured anti-probing minimum, since the receiver would decline it anyway — with an explanatory hint, and it reappears the instant the amount is raised or cleared. Adds the RECEIVE --> SETTINGS edge (payjoin settings long-press).
Adds Transaction.displayPayjoinStatus: when the broadcast transaction is visible its txid is authoritative (== payjoin txid -> completed, == original txid -> aborted), else the session status. The status label and details table consume it, and the table's status row becomes an exhaustive switch (no more raw status.name leaking to the user).
TransactionDetailsCubit reloads on payjoin events (not just syncs), resolves a resolved session's broadcast txid (local then a forced Result-based lookup) and re-enters by that txid so the screen lands on the real transaction instead of a session placeholder. The manual original-tx broadcast and its button gate on canManuallyBroadcastOriginal, with tightened subscription hygiene (cancel-before-replace, isClosed guards).
Adds a paged carousel of dismissible announcements on the wallet home, shown between the balance header and the wallet list. Ships two compile-time announcements: a payjoin-privacy nudge (once the wallet has transaction history and payjoin is off) and an autoswap-active notice. Each card is tappable to its target (payjoin/autoswap settings) and dismissible via an explicit x with a Read/Dismiss confirmation; the Read option opens the same target as tapping. Dismissals persist in the dismissed_announcements table with a permanent/snooze policy per announcement. The carousel adapts its height to the text scale and collapses to nothing when no announcement is visible. Built on bull_ui (BullInfoCard + PageView) and wired via a thin cubit that re-evaluates on payjoin-setting changes and wallet-sync completion.
feat(payjoin): settings, DB v14 migration, and live status
fix(send): resolve stuck sender flow on payjoin fallback (#2246)
… settings-fetch stuck-loading gap
feat(receive): payjoin toggle badge in TopBar with eligibility guard
feat(transactions): display payjoin status and land on broadcast tx
fix(labels): correct payjoin transaction labeling
feat(announcements): dismissible home announcement carousel
Recover stranded payjoin stack (pj/01-06)
|
@claude can you review this whole PR ? |
|
Claude finished @ethicnology's task in 2m 19s —— View job Review of PR #2499 — Payjoin FixesI reviewed the full diff vs CI is green, the branch is up to date with develop, FEATURES.md was updated (two graph inaccuracies noted below), and 🔴 Should fix before merge1. Two payjoin stream handlers lack the unhandled-error hardening this PR added to the third — 2. BIP-329 3. Toggle-off vs in-flight session creation race in the receive bloc — 🟡 Worth addressing (minor)Payjoin core (
Receive/send
Transactions
Settings / migration
Localization / FEATURES.md
🟢 Verified solid (checked hard, no issues)
SummaryThe payjoin session-resolution engine is the strongest part of this PR — nearly every race has an explicit re-fetch guard, a documented rationale citing an observed incident, and a test. The three fix-before-merge items are: carry the stream-handler hardening to the two sibling handlers, correct the BIP-329 pubkey validation (it now hard-rejects valid imports), and extend the toggle-race guard to the other two session-creation sites. Everything else is polish-level or a reasonable follow-up issue. Review method: full diff read across five areas by parallel review agents, with the top findings re-verified directly against the code before posting. |
|
@claude please add a follow up review |
|
Claude finished @i5hi's task in 4m 45s —— View job Follow-up review — verifying the fixes since the last reviewVerdict: all three fix-before-merge findings from the previous review (above in this thread) are properly fixed, each with a regression test. I re-read every fix commit in full context against the live code, checked for regressions and new edge cases the fixes might introduce, and found nothing blocking. CI for the fix commits was still running at review time (run 30092274219) — worth a glance before merge, but the changes are small and mechanical. Todo
✅ Issue 1 — stream-handler hardening (
|
Some work from payjoin-upgrade was not included in develop. Starting this PR to review it and merge in useful work.