fix: clean up deleted private contacts - #830
Conversation
|
jvsena42
left a comment
There was a problem hiding this comment.
Two MEDIUM and one LOW inline, all shared with synonymdev/bitkit-android#1372. I also replied on the cleanup thread: the remote private-list clear is skipped on every deletion. Paykit is on by default on master since #818 (unreleased).
Checked and clean:
removeContact/saveContactrun underoperationLock, so block → remove → unblock cannot interleave.- SDK handshakes reject blocked peers, so a racing link burst cannot re-link.
- The detached delete task is not cancelled with the view.
contactsRevisiondiscards a stale load.- The
restorePrivateConnection || existing != nilguard stops background refreshes recreating a deleted contact, and every explicit add/import passestrue. - Public markers cannot fire under
.localOnly. - Blocked state is scoped per identity.
- A partial block failure is recoverable by retrying the delete, as the new test asserts.
jvsena42
left a comment
There was a problem hiding this comment.
Re-checked 75e8c9c and 171d449.
Open: one LOW, posted as a reply on the subscription thread. Fixed-term subscriptions block deletion but cannot be cancelled.
Resolved: the test/journey history thread and the private-list clear ordering.
Checked and clean:
- The
activeSubscriptionrefusal propagates unwrapped, so the toast shows. (On Android it does not; see synonymdev/bitkit-android#1372.) - The re-add rollback re-blocks every originally blocked path on any failure, including cancellation, and rethrows the original error.
- The cached-approval path re-checks through
prepareForPaymentfor Lightning, LNURL, on-chain and HW, and cleans up the proof on denial. - The ContactsManager load loop prunes only after a successful load, and the generation guard stops a stale load from clearing
isLoading.
There was a problem hiding this comment.
Advice: ✅ Approve
Review: diff 17 files.
The paired Android PR differs; see the parity finding.
Findings:
3 inline (2 MEDIUM, 1 LOW)
QA:
Tests queued.
Reviewed by gpt-6-sol-xhigh via gh-pr-review-loop skill
Commands: @ovi-reviewer review · test · retest (author)
jvsena42
left a comment
There was a problem hiding this comment.
Re-checked 8d62210 and a182bae. The fixed-term refusal thread is resolved. Two LOWs inline: one on the new per-attempt HW authorization, one on the end-date rule.
Clean: the first HW attempt still runs prepare, then authorize, then broadcast, and a denial there fails and cancels the proof as before. isBroadcastUnresolved is set only after authorization passes, so a denied attempt never leaves the sheet undismissable.
There was a problem hiding this comment.
Advice: ✅ Approve
Reaudit: diff 5 files.
No new findings; the rest is in the review.
Pair PR synonymdev/bitkit-android#1372: equivalent.
QA:
Tests queued.
Reviewed by gpt-6-sol-xhigh via gh-pr-review-loop skill
Commands: @ovi-reviewer review · test · retest (author)
addressed - reaudit confirmed
jvsena42
left a comment
There was a problem hiding this comment.
Re-checked 9419700. No findings; both threads are resolved.
Checked and clean:
- Every broadcast attempt runs
ensurePaymentAllowed, so a blocked peer never gets a rebroadcast. - A denial throws before
isBroadcastUnresolvedis set, so the sheet stays dismissable. - Back after a denial-after-attempt keeps the started proof, so a re-sign is refused by
prepare. - The new tests cover both the attempted and the not-attempted denial.
There was a problem hiding this comment.
Verdict: ✅ Approve
Tests for the review: journeys J1 and J2 pass.
QA:
Tested on two iOS 26.5 simulators (iPhone 17 Pro), regtest.
Tests J1, J2 ✅
Test J1
Deletion held through a Contacts reopen and an app restart, a request sent after it never arrived, and a request after the re-add opened with the saved contact name.
J1.mp4 |
![]() |
Test J2
Deletion was refused with the active-subscription message until the subscription was canceled, and adding the contact again did not revive the canceled subscription.
J2.mp4 |
![]() | ![]() |
Note
After the contact was added again, the requests the requester still held as pending came back on the payer, including the ones sent before the deletion, and each opened as a payment sheet once the private link was back. This follows the SDK's blocked-peer behavior, which the PR leaves out of scope.
Reviewed by claude-opus-5-5-xhigh via gh-pr-review-loop skill
Commands: @ovi-reviewer review · test · retest (author)



Closes #838
This PR fixes private payments remaining available after a contact is deleted and contacts reappearing after a successful deletion.
Android companion: synonymdev/bitkit-android#1372
Description
Out of Scope
Design
N/A — no design available. Adds an error message to the existing contact-deletion toast.
Preview
VIDEO_1
QA Notes
Journeys
new
delete-and-readd-contact.xml— deletion survives refresh/restart, private requests stop, and explicit readd reconnects.new
delete-contact-with-active-subscription.xml— deletion requires the subscription to end, and re-add does not revive a canceled subscription.Both journeys are mirrored on iOS and Android and have not been run locally.
Manual Tests
N/A
Automated Checks
PaykitContactLifecycleTests.swift— receiver revocation, withdrawal before block including delivery failure, failed re-add retry, and subscription deletion guards for payer/payee, cancellation and expiry.ContactsManagerTests.swift— invalidated initial loads preserve unrelated contacts and reset stops a superseded load.PaykitPaymentRequestServiceTests.swift— blocked requests are not payable, cached approvals are rechecked without repeating acceptance, and subsequent history refresh matches SDK visibility.Deletion can still wait for existing SDK operations or endpoint-withdrawal network timeouts. No end-to-end deletion latency claim is made.