feat: support pubky contact deep links - #768
Conversation
|
There was a problem hiding this comment.
Verdict: ✅ Approve
Review: diff 8 files.
Counterpart synonymdev/bitkit-android#1320: differs only in bounding the readiness wait with a timeout; this one waits indefinitely.
Findings:
N/A
Audit:
Skipped - nothing a reviewer would report across 8 files (threshold 0.4; strongest Bitkit/MainNavView.swift at 0.35).
Coverage:
QA: journeys and manual tests await all reviewers to approve, author can run it now via comment: @ovi-reviewer test
Reviewed by claude-opus-5-xhigh via gh-pr-review-loop skill
Commands: @ovi-reviewer review · test · retest · audit (author or owner)
jvsena42
left a comment
There was a problem hiding this comment.
No HIGH or MEDIUM findings. Two LOWs inline, no verifier pass; read them as observations.
Checked and clean:
- No state change from the link. Routing only navigates (profile, contact detail, add contact). Save and pay stay button-driven, and
refreshContactReceiverPathsonly acts on saved contacts. - Parser. The charset is an allowlist with a fixed 52-char key, and
count <= 57is checked before normalization. Path, fragment, userinfo, port and extra or duplicate params are rejected. The negative vectors match Android's. - Precedence. A contact link never waits on LDK. A malformed one is rejected without waiting on Pubky readiness.
- Locked app.
MainNavViewisn't mounted beforeisPinVerified, so the link waits and routes exactly once after unlock. - Logging and sign-out.
sanitizedDeeplinkDescriptionstrips the query before logging. Sign-out mid-wait recomputes readiness, and nothing is attributed to another identity. - Journey. The file name, journey name and all 15 actions are byte-identical to Android's; only the platform launch command in
<description>differs.
There was a problem hiding this comment.
Verdict: ✅ Approve
Reaudit: diff 2 files.
Counterpart synonymdev/bitkit-android#1320 still bounds the readiness wait with a timeout; this delta does not change that.
Findings:
N/A
Audit:
Already done in comment.
Coverage:
QA: journeys and manual tests await all reviewers to approve, author can run it now via comment: @ovi-reviewer test
Reviewed by claude-opus-5-xhigh via gh-pr-review-loop skill
Commands: @ovi-reviewer review · test · retest · audit (author or owner)
jvsena42
left a comment
There was a problem hiding this comment.
Delta since 92935754 (2e58180a): no findings. Both LOWs are fixed. isContactDeepLinkReady moved out of the .task id into its own .onChange, which spawns an unstructured Task, so contacts finishing their load no longer cancel an in-flight handler. With Paykit off, pubkyContactPublicKeyForRouting returns nil before parsing, so the link is dropped silently. With Paykit on, a malformed link still throws.
There was a problem hiding this comment.
Verdict: ⛔️ Request Changes
Tests for the review: 3 of 6 manual tests passed.
QA:
Tested on iOS 26.5 simulator (iPhone 17 Pro)
Test 1 ✅ passed
Test 2 ⛔️ failed: Saved contact QA724PIN, relaunched, and reopened its bitkit://contact link. It showed Add Contact with "RETRIEVING CONTACT INFO" instead of Contact Detail.
Test 3 ✅ passed
Test 4 ⛔️ failed: Enabled PIN 1111, terminated the app, and opened the QA724PIN contact link while locked. After unlock it again showed Add Contact instead of Contact Detail.
Test 5 ✅ passed
Test 6 ⛔️ failed: Generated a QR for a valid Pubky key, added it to Photos, opened Scan, and picked it from the photo picker; the picker dismissed without opening Add Contact. A second attempt with a higher-error-correction QR image had the same result.
Tip
Worth a journey
Test 1
- Enable Paykit UI in Settings → Advanced → Dev Settings
- Return to Wallet
- Open a contact deep link for an unsaved Pubky key
- Verify Add Contact shows the key and Save action
- Verify nothing is saved before tapping Save
Test 3
- Create and activate a Pubky profile
- Copy the wallet's own Pubky key
- Return to Wallet
- Open a contact deep link for the own key
- Verify Profile opens with the same Pubky key
- Verify Add Contact does not open
Test 5
- Return to Wallet
- Open a contact link without a Pubky parameter
- Verify the error clears back to Wallet without another flow
- Open a contact link with duplicate Pubky parameters
- Verify the error clears back to Wallet without another flow
- Open a contact link with an invalid Pubky parameter
- Verify the error clears back to Wallet without another flow
Reviewed by gpt-5.6-sol-high via gh-pr-review-loop skill
Commands: @ovi-reviewer review · test · retest · audit (author or owner)
|
Saved contact links now wait for local contact records to load even when the wallet has no active Pubky identity, so the relaunch and PIN-unlock cases route to Contact Detail. The photo-picker result stopped in Vision with |
There was a problem hiding this comment.
Verdict: ⛔️ Request Changes
Reaudit: diff 2 files.
New findings: 1 inline (1 blocking); the rest is in the review.
Retest suggested: Tests 2, 4 (this change affects saved-contact routing and cold-start replay).
Counterpart synonymdev/bitkit-android#1320: diverges, see the parity finding.
Coverage:
Unit tests: 55% - PubkyContactLinkTests covers the readiness gate but not preload failure and recovery.
QA: journeys and manual tests await green CI checks
Reviewed by gpt-5.6-sol-high via gh-pr-review-loop skill
Commands: @ovi-reviewer review · test · retest · audit (author or owner)
|
Cold-start contact links now wait for the initial contact load and retry after a failure before routing. Contact preparation no longer depends on Lightning node state, and the link stays queued if recovery still fails. |
There was a problem hiding this comment.
Advice: ✅ Approve
Reaudit: diff 3 files.
No new findings; the rest is in the review.
Retest suggested: Tests 1-4 (it now preloads contacts for every Paykit wallet before routing a contact link, including the cold-start case).
Counterpart synonymdev/bitkit-android#1320: equivalent.
Reviewed by claude-opus-5-5-xhigh via gh-pr-review-loop skill
Commands: @ovi-reviewer review · test · retest · audit (author or owner)
jvsena42
left a comment
There was a problem hiding this comment.
Delta since 2e58180a (9bf0252be, a0a768425): no findings.
- The pending link now preloads contacts before routing, and
canRoutePubkyContactLinkrequireshasLoadedContactswhenever Paykit UI is on, so a saved contact is never routed from an empty list. isPreparingPendingContactDeepLinkkeeps one preload in flight. A second call falls through to routing, which is not ready yet and leaves the URL pending.loadContactsIfNeedednow checks cancellation inside theisLoadingwait, so a cancelled task cannot spin on the publisher.- The node-running trigger is split into its own
.task, which skips contact links, so LDK coming up no longer cancels an in-flight contact preload. - Nit, not a finding:
loadContactsForPendingDeepLinkIfNeededfalls back to the link's own key ascontactsOwnerPublicKey.loadContacts(for:)only logs that value and reads records from the SDK, so the effect is a log line naming the counterparty as the owner.
jvsena42
left a comment
There was a problem hiding this comment.
Passed on the 6 manual tests






This PR adds
bitkit://contact?pubky=<public-key>links that open the existing Pubky scanner contact flow.Android counterpart: synonymdev/bitkit-android#1320
Description
Example:
The value may be a raw 52-character key or include the
pubkyprefix. URL-encode the value. Existing Paykit feature gating is unchanged.Out of Scope
Design
N/A — no UI changes. Reuses existing Add Contact, Contact Detail and Profile screens.
Preview
N/A — existing scanner screens are unchanged.
QA Notes
Manual Tests
pubkyparameters: no contact, payment or auth flow opens.Automated Checks
PubkyContactLinkTests.swift: accepted key forms, malformed links and non-key payloads, retained unlock/contact-readiness gate, no Lightning dependency and scanner routing.ContactsManagerTests.swiftandPubkyAuthURLSchemeTests.swiftcover known/unknown/own-key routing, feature gating and auth/payment deep-link regressions.