@@ -74,6 +74,10 @@ abstract class SendState with _$SendState {
7474 // fetched settings at least once, no payjoin is attempted. Mirrors
7575 // SettingsEntity.isPayjoinEnabled.
7676 @Default (false ) bool payjoinGloballyEnabled,
77+ // The sender's per-send opt-out: payjoin can be available for this send
78+ // (see [isPayjoinAvailable]) and still deliberately not attempted. Reset
79+ // is not needed — the cubit lives per send flow.
80+ @Default (false ) bool payjoinOptedOut,
7781 @Default ('' ) String amount,
7882 int ? confirmedAmountSat,
7983 BitcoinUnit ? bitcoinUnit,
@@ -176,25 +180,30 @@ abstract class SendState with _$SendState {
176180 /// Whether we have a valid payment request
177181 bool get hasValidPaymentRequest => paymentRequest != null ;
178182
179- /// Single source of truth for whether a payjoin will actually be attempted
180- /// for this send — same pattern as [Payjoin.canManuallyBroadcastOriginal] ,
181- /// which unifies a button's visibility and its action guard. Used BOTH to
182- /// gate `signTransaction` 's payjoin branch and to show the "a payjoin will
183- /// be attempted" indicator on the confirm screen, so the two can never
184- /// disagree.
183+ /// Whether a payjoin is structurally possible for this send: the setting
184+ /// is on, the wallet signs locally, and the recipient's BIP21 advertises a
185+ /// pj= endpoint. Drives whether the confirm screen offers the payjoin
186+ /// toggle at all — [willAttemptPayjoin] adds the sender's choice on top.
185187 ///
186188 /// Gated on [Wallet.signsLocally] : a hardware/remote-signer wallet
187189 /// (Ledger/BitBox) never reaches `signTransaction` 's payjoin branch (the
188190 /// confirm screen swaps in a device-specific sign button for those
189- /// wallets instead), so without this check the indicator could promise a
191+ /// wallets instead), so without this check the toggle could promise a
190192 /// payjoin that structurally can never happen for that wallet class.
191- bool get willAttemptPayjoin =>
193+ bool get isPayjoinAvailable =>
192194 payjoinGloballyEnabled &&
193195 (selectedWallet? .signsLocally ?? false ) &&
194196 isToSelf != true &&
195197 paymentRequest is Bip21PaymentRequest &&
196198 (paymentRequest! as Bip21PaymentRequest ).pj.isNotEmpty;
197199
200+ /// Single source of truth for whether a payjoin will actually be attempted
201+ /// for this send — same pattern as [Payjoin.canManuallyBroadcastOriginal] ,
202+ /// which unifies a control's state and its action guard. Used BOTH to
203+ /// gate `signTransaction` 's payjoin branch and as the confirm screen's
204+ /// toggle value, so the two can never disagree.
205+ bool get willAttemptPayjoin => isPayjoinAvailable && ! payjoinOptedOut;
206+
198207 String get paymentRequestAddress {
199208 if (paymentRequest == null ) {
200209 return copiedRawPaymentRequest.isNotEmpty
0 commit comments