Skip to content

fix: add quickpay daily spend limit - #672

Draft
ovitrif wants to merge 32 commits into
masterfrom
fix/670-quickpay-day-limit
Draft

fix: add quickpay daily spend limit#672
ovitrif wants to merge 32 commits into
masterfrom
fix/670-quickpay-day-limit

Conversation

@ovitrif

@ovitrif ovitrif commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator

Closes #670

This PR ports QuickPay daily spend limits from Android (synonymdev/bitkit-android#1159) so auto-pay stays PIN-free under a configurable daily cap.

Description

QuickPay stays confirmation-free and PIN-free while a payment is under the per-tx threshold and the daily cap (threshold × multiplier USD). Once the cap would be exceeded, the payment opens Confirm, where PIN for payments still applies if that setting is on.

  • Daily multiplier steps: 1, 3, 5, 10, 50 (default 5)
  • Spend is reserved in USD cents before pay, kept on Success/Pending, and released on failure only from the matching calendar day
  • Spend resets on the local calendar day and is kept if the clock rolls back
  • Settings add a second slider and show the resolved daily $ cap
  • The multiplier is included in settings backup as quickPayDailyLimitMultiplier
  • Slider labels and the currency-conversion error are localized. Over-cap is silent Confirm
  • Successful QuickPay shows amount plus the paid routing fee
  • Over-cap or a lost reserve race replaces QuickPay with Confirm, so Back does not return to the auto-pay loader
  • Try Again remounts QuickPay so a new payment starts

Preview

QuickPay Settings Over Cap → Confirm + PIN
happy-daily-cap.mp4
over-cap.mp4
Screenshot

QA Notes

Manual Tests

  • 1. Settings → QuickPay → enable QuickPay → move threshold and daily multiplier sliders: resolved daily $ copy updates (threshold × multiplier).
  • 2. Unlocked, under per-tx and daily cap, PIN for payments on → scan a fixed-amount LN invoice: QuickPay runs with no payment PIN.
  • 3. Exhaust or exceed the daily cap → scan another eligible invoice: Confirm opens; payment PIN is required if that setting is on.
  • 4. regression: Settings → QuickPay → leave QuickPay off → scan a fixed-amount LN invoice: Confirm still opens.
  • 5. regression: Variable amount LN invoice / LNURL-pay: still lands on Amount, not QuickPay.
  • 6. QuickPay success: displayed amount includes the paid routing fee.
  • 7. Over daily cap → Confirm: Back does not return to the QuickPay loader.
  • 8. QuickPay failure → Try Again: a new payment starts.

Automated Checks

  • Unit tests added: day-keyed USD-cent spend reserve/release/reset and clock-rollback keep-spend in BitkitTests/QuickPaySpendStoreTests.swift.
  • Unit tests added: QuickPay routing under the cap, at the cap, and over the cap, plus replace-QuickPay navigation, in BitkitTests/PaymentNavigationHelperTests.swift.
  • Unit tests added: fee-inclusive success amount and multiplier fallback in BitkitTests/QuickPayLimitsTests.swift.
  • Unit tests modified: backup/restore of the daily multiplier, including the Android key, in BitkitTests/AddressTypeSettingsTests.swift.
  • node scripts/validate-translations.js: 0 errors.
  • Focused unit tests passed locally on iPhone 16 (iOS 18.5).
  • CI: standard build and test checks run by the PR bot.

Track USD spend on the local calendar day so QuickPay can enforce a daily cap.
Expose the Android daily multiplier steps and resolved dollar cap in settings, and keep the value in backup.
Keep auto-pay PIN-free under the daily limit, record spend on success or pending, and send over-cap payments to Confirm.
@ovitrif ovitrif changed the title fix: add QuickPay daily spend limit fix: add quickpay daily spend limit Aug 18, 2026
@ovitrif ovitrif self-assigned this Aug 18, 2026
@ovitrif ovitrif added this to the 2.5.0 milestone Aug 18, 2026
@ovitrif
ovitrif marked this pull request as ready for review August 18, 2026 16:30
@ovitrif
ovitrif requested review from ben-kaufman and pwltr August 18, 2026 16:30
@greptile-apps

greptile-apps Bot commented Aug 18, 2026

Copy link
Copy Markdown

Greptile Summary

The PR adds a persisted, day-keyed QuickPay spending cap, routes over-cap payments through confirmation, and carries reservations through pending payment resolution.

  • Adds configurable daily multipliers and backup/restore support.
  • Reserves converted USD spend before QuickPay and releases failed reservations.
  • Updates send navigation, pending resolution, fee-inclusive success amounts, localization, and focused tests.

Confidence Score: 4/5

The PR is not yet safe to merge because an immediately settling payment can still leave a failed QuickPay reservation consuming the daily cap.

The previous thread's final reply said the listener-before-binding order resolved the race, but terminal events are also handled independently by AppViewModel and can clear or release the hash after send returns but before the separately scheduled watch task binds the reservation.

Files Needing Attention: Bitkit/ViewModels/WalletViewModel.swift, Bitkit/Views/Wallets/Send/SendQuickpay.swift, Bitkit/ViewModels/AppViewModel.swift

Important Files Changed

Filename Overview
Bitkit/ViewModels/WalletViewModel.swift Adds timeout-aware payment watching, but defers reservation binding until a child watch task runs, preserving an early-terminal-event race.
Bitkit/Views/Wallets/Send/SendQuickpay.swift Reserves spend and binds it through afterListening while retaining timed-out reservations for later terminal resolution.
Bitkit/Utilities/QuickPaySpendStore.swift Implements synchronized daily spend and hash-keyed reservation persistence with idempotent terminal cleanup.
Bitkit/ViewModels/AppViewModel.swift Resolves pending QuickPay payments and clears or releases their reservations on terminal events.
Bitkit/Utilities/PaymentNavigationHelper.swift Applies the new daily-cap check when selecting QuickPay and provides replacement navigation for over-cap payments.

Reviews (3): Last reviewed commit: "chore: drop QuickpaySettings preview" | Re-trigger Greptile

Comment thread Bitkit/Views/Wallets/Send/SendQuickpay.swift Outdated
ben-kaufman

This comment was marked as resolved.

@ovitrif
ovitrif requested a review from ben-kaufman August 18, 2026 22:02

@ben-kaufman ben-kaufman left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Three new findings from the re-audit.

Comment thread Bitkit/Utilities/QuickPaySpendStore.swift Outdated
Comment thread Bitkit/Views/Wallets/Send/SendPendingScreen.swift Outdated
Comment thread Bitkit/Views/Wallets/Send/SendQuickpay.swift Outdated
@ovitrif ovitrif removed this from the 2.5.0 milestone Aug 19, 2026
@ovitrif

This comment was marked as outdated.

@ovitrif
ovitrif marked this pull request as draft August 19, 2026 13:59
@ovitrif
ovitrif marked this pull request as ready for review August 19, 2026 22:21
Comment thread Bitkit/Utilities/QuickPayLimits.swift Outdated
@pwltr

pwltr commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Tested the QuickPay daily limit flow on local regtest with fixed-amount hold invoices.

Covered:

  • QuickPay under the per-payment threshold and daily cap stays PIN-free and moves to Pending after the hold invoice timeout.
  • Settling the hold invoice moves Pending -> Success.
  • Canceling the hold invoice moves Pending -> Failure.
  • Exhausting the daily cap routes the next eligible fixed invoice to Confirm without returning to the QuickPay loader on Back.
  • Failed QuickPay reservations are released, while successful/pending spend remains counted against the daily cap.
  • Try Again from QuickPay failure starts a fresh payment attempt.
  • QuickPay-off and variable-amount invoice routes still go through the expected non-QuickPay screens.

The timeout now starts after lightningService.send returns the payment hash. That seems fine if LDK submission is expected to return quickly, but it’s worth keeping in mind that pre-hash submission time is no longer covered by the pending-screen timeout.

I also reviewed the code structure around QuickPaySpendStore, QuickPayLimits, and the send navigation changes. The implementation is reasonably contained and the focused unit tests cover the important ledger/day-boundary behavior. No blocking issues from my side.

Comment thread Bitkit/Models/SettingsBackupConfig.swift
Comment thread Bitkit/Models/BackupPayloads.swift Outdated
@ovitrif
ovitrif marked this pull request as draft August 20, 2026 19:17
@ovitrif
ovitrif marked this pull request as ready for review August 21, 2026 08:14
@ovitrif
ovitrif removed the request for review from piotr-iohk August 21, 2026 08:16
Comment thread Bitkit/ViewModels/WalletViewModel.swift Outdated
@ovitrif
ovitrif requested a review from piotr-iohk August 21, 2026 08:27
@ovitrif

This comment was marked as resolved.

@ovitrif
ovitrif marked this pull request as draft August 21, 2026 08:27
Bind the reservation to the invoice hash before send. AppViewModel only
routes events to noteTerminal. Persist a canonical ledger and reconcile
against LDK without treating absence as failure.
Write only the canonical ledger. Drop unused phase/dispatch flags and
collapse send-error classification to a Bool.
Restore from the Android three-field snapshot only. Local store stays a
ledger. Drop unused record id and ledger version.
Write and restore quickPayAmount so the per-tx threshold round-trips
with the daily multiplier.
Duplicate is NodeError.DuplicatePayment only.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Port QuickPay daily spend limit from Android

4 participants