feat: support incoming paykit requests - #637
Conversation
Greptile SummaryThis PR adds support for receiving and paying incoming Paykit requests. The main changes are:
Confidence Score: 4/5The backup migration and payment submission paths need fixes before merging.
PrivatePaykitService+Backup.swift, LnurlPayConfirm.swift, and SendConfirmationView.swift
|
| Filename | Overview |
|---|---|
| Bitkit/Services/PaykitPaymentRequestService.swift | Adds request parsing, synchronization, expiration handling, presentation deduplication, and acceptance state. |
| Bitkit/Services/PrivatePaykitService+Payments.swift | Adds private request resolution, public fallback rules, and consumed-version enforcement. |
| Bitkit/Services/PrivatePaykitService+Backup.swift | Wraps Paykit backup state in a new JSON shape without restoring the previous raw-string format. |
| Bitkit/Views/Wallets/Send/LnurlPayConfirm.swift | Accepts and consumes incoming requests before the LNURL invoice is fetched. |
| Bitkit/Views/Wallets/Send/SendConfirmationView.swift | Integrates request acceptance and consumption but can submit an embedded invoice amount that differs from the requested amount. |
| Bitkit/AppScene.swift | Adds active-scene polling and serialized request presentation through the send sheet. |
| Bitkit/Services/PubkyService.swift | Adapts the Paykit integration to separate public and private rc39 APIs and advertises request support. |
Sequence Diagram
sequenceDiagram
participant Peer as Paykit Peer
participant Manager as Request Manager
participant Resolver as Private Resolver
participant UI as Send Confirmation
participant Wallet
Peer->>Manager: Proposed payment request
Manager->>Resolver: Resolve endpoint for requested amount
Resolver-->>UI: Payment target and list version
UI->>Manager: Accept request
UI->>Resolver: Persist consumed version
UI->>Wallet: Fetch invoice and submit payment
Note over UI,Wallet: LNURL fetch currently happens after acceptance and consumption
Reviews (1): Last reviewed commit: "feat: support incoming paykit requests" | Re-trigger Greptile
990db23 to
e4a5c92
Compare
ae345b3 to
778f6d0
Compare
778f6d0 to
9dcc431
Compare
| let amountValue: String | ||
| let amountSats: UInt64 | ||
| let paymentReference: String | ||
| let expiresAt: Date? |
There was a problem hiding this comment.
is this intended to be optional? just double checking
| for request in requests { | ||
| do { | ||
| let result = try await PrivatePaykitService.shared.beginPaymentRequest(request) | ||
| guard sheets.activeSheetConfiguration == nil, app.contactPaymentContext == nil else { return } | ||
| guard case let .opened(paymentTarget, privatePaymentContext) = result else { continue } |
There was a problem hiding this comment.
if beginPaymentRequest never returns .opened (the counterparty has no private payment list, or no accepted endpoint matches) this method takes the continue branch and markPresentedIfPending is never called. That request then re-runs full SDK resolution on every 30 s poll and on every sheet dismissal, indefinitely, with no backoff and no user-visible feedback
| private func pollIncomingPaykitPaymentRequests() async { | ||
| guard scenePhase == .active else { return } | ||
|
|
||
| while !Task.isCancelled { | ||
| do { | ||
| try await Task.sleep(for: Self.paykitPaymentRequestRefreshInterval) | ||
| } catch { | ||
| return | ||
| } | ||
| await refreshIncomingPaykitPaymentRequests() | ||
| } | ||
| } |
There was a problem hiding this comment.
This method perform multiple requests every 30 seconds for as log the app is in foreground, even if nothings has changed
Maybe should back off the requests when nothing has been returned or scaling the interval by linked-peer count
| } | ||
| } | ||
|
|
||
| private func hasLiveSessionForCurrentProfile() async throws -> Bool { |
There was a problem hiding this comment.
this method has no remaining callers. Dead code?
| try await consumePrivatePaymentListIfNeeded(contactPaymentContext) | ||
| let txid = try await wallet.send(address: invoice.address, sats: amount, isMaxAmount: useMaxAmount) | ||
| if let contactPublicKey { | ||
| await PrivatePaykitService.shared.discardRemoteOnchainEndpoints( |
There was a problem hiding this comment.
This was the last caller of this method. Is it dead code?
Description
This PR builds on #630 to support incoming Paykit payment requests:
0.1.0-rc39and uses its separate public and private payment resolution APIs.Payment proofs and receipts remain out of scope.
Linked Issues/Tasks
Screenshot / Video
N/A — the existing payment UI is reused, and no media is attached.
QA Notes
Manual Tests
Automated Checks
PaykitPaymentRequestServiceTests.swift: request mapping, expiry, lifecycle, acceptance, and refresh concurrency.PrivatePaykitServiceTests.swift: consumed-list persistence and prevention of private payment-detail reuse.PublicPaykitServiceTests.swift: separate public payment endpoint parsing and resolution.git diff --checkpassed.