feat: add Paykit server accounts - #630

Merged
jvsena42 merged 19 commits into
masterfrom
codex/paykit-watch-only-accounts
Jul 24, 2026
Merged

feat: add Paykit server accounts#630
jvsena42 merged 19 commits into
masterfrom
codex/paykit-watch-only-accounts

Conversation

@ben-kaufman

@ben-kaufmanben-kaufman commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Description

This PR builds on #620 to add Paykit Server watch-only accounts:

  • Presents a dedicated watch-only consent step before the normal Pubky authorization screen.
  • Requires x-bitkit-claim=watch-only-account-v1 and the exact /pub/paykit/v0/bitkit/server/:rw capability, rejecting missing, unknown, duplicate, or mismatched companion parameters.
  • Uses Paykit companion authorization and fails closed if companion delivery or normal authorization fails.
  • Allocates monotonic BIP84 accounts, reuses the same incomplete account on retry, and exports the exact versioned 84-byte claim payload.
  • Automatically names new accounts from the requesting service; users can rename them later from Settings.
  • Hides Settings → Advanced → Server Accounts until the experimental Paykit UI is enabled.
  • Persists and backs up account metadata, serializes lifecycle and reconciliation changes, and supports disabling or restoring LDK tracking without deleting wallet state.
  • Configures additional-wallet scanning and the rolling 1,000-address lookahead.
  • Deterministically derives the receiver Noise key from the wallet's BIP39 seed using domain-separated HKDF-SHA256 with version, network, and receiver-path context; securely caches and verifies it so the wallet seed remains the only backup required.
  • Reuses the derived Noise key across authentication and restarts, and publishes only its receiver marker when live session access becomes available.
  • Updates to Paykit 0.1.0-rc37, LDK Node 0.7.0-rc.57, and Bitkit Core 0.4.2; Bitkit Core now supplies trusted extended-public-key serialization, so Base58Swift and its BigInt dependency are no longer required.

The relay claim contains only the versioned account index, address type, and serialized xpub. Local names and tracking state are not disclosed. Ordinary Pubky Auth remains unchanged.

References:

QA Notes

This slice implements ADR 0001's watch-only-account-v1 claim, which is the currently agreed protocol. ADR 0002's proposed watch-only-account-service-v1 delegation flow has not been agreed and is not part of the current implementation.

Reviewers can exercise the complete Bitkit-side setup and account lifecycle with this disposable test request:

pubkyauth://signin?caps=/pub/paykit/v0/bitkit/server/:rw&relay=https://httprelay.pubky.app/inbox/&secret=e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3s&x-bitkit-claim=watch-only-account-v1

Enable Paykit UI, then scan or paste the URL. It exercises watch-only consent, normal Pubky authorization, account allocation and xpub export, companion-claim delivery followed by AuthToken delivery, local activation, and Server Accounts management. The xpub can be copied from the account details and used for external address derivation, funding, restart discovery, spending, and tracking disable/re-enable testing.

The request secret is public, so this URL must only be used with a disposable regtest wallet. A Paykit Server listener or receiver harness is still required to verify requester-side relay polling, decryption, and signature verification. A staging deployment is intended once Paykit Server runtime/listener composition is available.

Manual Tests

  • Enable Paykit UI → Settings → Advanced: Server Accounts is visible; disable Paykit UI: it is hidden.
  • Scanner/paste → use the disposable setup request above → approve watch-only sharing → complete normal Pubky authorization: the account becomes active.
  • Copy the xpub from Server Accounts → externally derive a nonzero receive address → send funds → restart Bitkit: the additional-wallet full scan discovers the funds.
  • Spend the externally received funds from Bitkit.
  • Settings → Advanced → Server Accounts → disable and re-enable tracking: the same account and balance return without allocating another account.
  • Missing, duplicate, unknown, or mismatched companion parameters/capability are rejected; ordinary Pubky Auth remains unchanged.

Automated Checks

  • WatchOnlyAccountServiceTests.swift
  • PubkyAuthApprovalSheetTests.swift
  • PubkyAuthRequestTests.swift
  • PaykitReceiverNoiseKeyStoreTests.swift
  • Paykit rc37 simulator build succeeded against LDK Node rc.57.
  • The exact E2E app-build mode (DEBUG E2E_BUILD) succeeds with the notification target's watch-only/localization membership dependencies.
  • The iOS and Android receiver-key tests share an independently verified derivation vector and cover persistence, reuse, invalid caches, and wrong-seed fail-closed behavior.
  • Post-delivery normal-auth failure, retry after delivery, restart retry, activation, and wallet Paykit regressions are covered by the selected suites.
  • 101 selected watch-only, Paykit, Pubky Auth, endpoint, and receiver-key tests passed after rebase.
  • SwiftFormat and git diff --check passed.

@greptile-apps

greptile-appsBot commented Jul 15, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds Paykit Server watch-only accounts. The main changes are:

  • Strict parsing for the watch-only companion claim and capability.
  • Companion authorization with tracked setup and retry states.
  • Monotonic account allocation, backup, restore, and reconciliation.
  • LDK scanning and tracking controls for additional wallet accounts.
  • Deterministic receiver Noise-key derivation and persistence.
  • Settings screens for managing account names and tracking.

Confidence Score: 5/5

This looks safe to merge.

  • The updated delivery-error handling keeps the account tracked after the documented post-delivery authorization failure.
  • No blocking issue remains in the reviewed changes.

Important Files Changed

FilenameOverview
Bitkit/Services/PubkyService.swiftAdds companion authorization and preserves account tracking for the documented post-delivery authorization failure.
Bitkit/Services/WatchOnlyAccountService.swiftAdds account allocation, persistence, authorization lifecycle, backup restoration, and tracking reconciliation.
Bitkit/Services/LightningService.swiftIntegrates watch-only accounts with LDK startup, scanning, address lookahead, and tracking controls.

Reviews (3): Last reviewed commit: "fix: derive Paykit Noise key from wallet..." | Re-trigger Greptile

Comment threadBitkit/Services/PubkyService.swift
@ben-kaufman
ben-kaufman marked this pull request as draft July 15, 2026 21:23
@ben-kaufman
ben-kaufman marked this pull request as ready for review July 16, 2026 15:19
@ben-kaufman
ben-kaufman marked this pull request as draft July 20, 2026 11:18
@ben-kaufman
ben-kaufman marked this pull request as ready for review July 20, 2026 11:48
@piotr-iohk

Copy link
Copy Markdown
Collaborator

Same review notes as the companion Android PR: synonymdev/bitkit-android#1084 (comment)

(Please apply the same UI gating / testing / ADR questions there.)

CI / merge

  • Merge conflicts with the base branch (codex/paykit-rc33-receivers) — please rebase/resolve.
  • E2E build-local / build-staging currently fail: LightningService references WatchOnlyAccountRecord from the BitkitNotification target where that type isn’t in scope. Please fix so CI builds again.

@ben-kaufman
ben-kaufmanforce-pushed the codex/paykit-watch-only-accounts branch from 67d11d0 to 3d5c9beCompareJuly 22, 2026 10:15
Base automatically changed from codex/paykit-rc33-receivers to masterJuly 22, 2026 11:10
@ben-kaufman
ben-kaufmanforce-pushed the codex/paykit-watch-only-accounts branch from 990db23 to e4a5c92CompareJuly 22, 2026 11:39
@ben-kaufmanChatGPT Codex Connector

Copy link
Copy Markdown
ContributorAuthor

Thanks — the corresponding iOS items are now addressed on the latest commit.

UI gating

Settings → Advanced → Server Accounts is now only visible when Enable Paykit UI is enabled.

Testing the flow

Reviewers can exercise the Bitkit-side flow using this disposable request:

pubkyauth://signin?caps=/pub/paykit/v0/bitkit/server/:rw&relay=https://httprelay.pubky.app/inbox/&secret=e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3s&x-bitkit-claim=watch-only-account-v1

Enable Paykit UI, then paste or scan it. This exercises the watch-only consent, normal Pubky authorization, account allocation/xpub export, both relay deliveries, local activation, and Server Accounts UI.

The xpub can then be copied from the account details sheet and used to test external address derivation, funding, restart discovery, spending, and disabling/re-enabling tracking. Because the request secret is public, this should only be done with a disposable regtest wallet.

The missing part is requester-side polling, decryption, and signature verification; that requires a Paykit Server listener or receiver harness.

Server staging

The intended next server-side QA step is to deploy Paykit Server on staging once runtime/listener composition exists. There is no staging endpoint yet, and that work is outside these mobile PRs.

Retry and activation behavior

The same post-delivery behavior is covered on iOS:

  • Paykit's documented post-delivery AuthorizationFailure leaves the account authorizing and tracked.
  • Retries and retries after restart reuse the same account index, xpub, and payload.
  • Pre-delivery failures return newly pending accounts to an unloaded state.
  • Activation is protected from caller cancellation.
  • A local activation failure after remote delivery is recovered by retrying the same combined authorization and activating the same account.

Protocol version

ADR 0001's watch-only-account-v1 is the currently agreed protocol and is intentionally implemented here. ADR 0002's proposed watch-only-account-service-v1 delegation flow has not been agreed and is not part of the current implementation.

CI and regression

The branch was rebased and is currently mergeable. The notification-target failure was fixed through the target membership exclusion list; build-local and build-staging now pass. The selected 101 watch-only, Pubky Auth, endpoint, receiver-key, and wallet Paykit regression tests pass.

The existing pubky_paykit E2E fixture issue uses outdated pre-made profiles and reproduces on the base PR, so it is inherited CI noise rather than a regression from this watch-only change.

@piotr-iohk

Copy link
Copy Markdown
Collaborator

Same retest notes as the companion Android PR: synonymdev/bitkit-android#1084 (comment)

iOS-specific bits already covered there: Enter-manually gap for pubkyauth, timeout → Incomplete setup → retry OBS, and the icon/title/copy parity nits.

@ovitrif
ovitrif requested a review from piotr-iohkJuly 23, 2026 20:04
@piotr-iohk

Copy link
Copy Markdown
Collaborator

Retested after the alignment fixes. Looks good to approve on my side.

Verified:

  • Settings row icon / title casing aligned
  • Server Accounts header burger present
  • pubkyauth limited to main scan (Send paste / Enter manually reject as intended)
  • Disposable watch-only setup URL → consent → authorize → Server Accounts still works

No remaining iOS blockers from this pass. Android still has a home-Scan gating bug (home Scan is modeled as Send, so pubkyauth is rejected there); noted on the companion PR.

@jvsena42jvsena42 added this to the 2.5.0 milestone Jul 24, 2026

@jvsena42jvsena42 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Simulator.Screen.Recording.-.iPhone.17.-.2026-07-24.at.10.05.22.mov

Approved with some nits

Comment threadBitkit/Services/PubkyService.swift
Comment threadBitkit/Managers/ScannerManager.swift
@jvsena42
jvsena42 merged commit 95c1766 into masterJul 24, 2026
11 checks passed
@jvsena42
jvsena42 deleted the codex/paykit-watch-only-accounts branch July 24, 2026 13:48
@jvsena42jvsena42 mentioned this pull request Jul 27, 2026
13 tasks
Sign up for freeto 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.

4 participants

@ben-kaufman@piotr-iohk@jvsena42@MrxiaoAxiao
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

feat: add Paykit server accounts - #630

Merged
jvsena42 merged 19 commits into
masterfrom
codex/paykit-watch-only-accounts
Jul 24, 2026
Merged

feat: add Paykit server accounts#630
jvsena42 merged 19 commits into
masterfrom
codex/paykit-watch-only-accounts

Conversation

@ben-kaufman

@ben-kaufmanben-kaufman commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Description

This PR builds on #620 to add Paykit Server watch-only accounts:

  • Presents a dedicated watch-only consent step before the normal Pubky authorization screen.
  • Requires x-bitkit-claim=watch-only-account-v1 and the exact /pub/paykit/v0/bitkit/server/:rw capability, rejecting missing, unknown, duplicate, or mismatched companion parameters.
  • Uses Paykit companion authorization and fails closed if companion delivery or normal authorization fails.
  • Allocates monotonic BIP84 accounts, reuses the same incomplete account on retry, and exports the exact versioned 84-byte claim payload.
  • Automatically names new accounts from the requesting service; users can rename them later from Settings.
  • Hides Settings → Advanced → Server Accounts until the experimental Paykit UI is enabled.
  • Persists and backs up account metadata, serializes lifecycle and reconciliation changes, and supports disabling or restoring LDK tracking without deleting wallet state.
  • Configures additional-wallet scanning and the rolling 1,000-address lookahead.
  • Deterministically derives the receiver Noise key from the wallet's BIP39 seed using domain-separated HKDF-SHA256 with version, network, and receiver-path context; securely caches and verifies it so the wallet seed remains the only backup required.
  • Reuses the derived Noise key across authentication and restarts, and publishes only its receiver marker when live session access becomes available.
  • Updates to Paykit 0.1.0-rc37, LDK Node 0.7.0-rc.57, and Bitkit Core 0.4.2; Bitkit Core now supplies trusted extended-public-key serialization, so Base58Swift and its BigInt dependency are no longer required.

The relay claim contains only the versioned account index, address type, and serialized xpub. Local names and tracking state are not disclosed. Ordinary Pubky Auth remains unchanged.

References:

QA Notes

This slice implements ADR 0001's watch-only-account-v1 claim, which is the currently agreed protocol. ADR 0002's proposed watch-only-account-service-v1 delegation flow has not been agreed and is not part of the current implementation.

Reviewers can exercise the complete Bitkit-side setup and account lifecycle with this disposable test request:

pubkyauth://signin?caps=/pub/paykit/v0/bitkit/server/:rw&relay=https://httprelay.pubky.app/inbox/&secret=e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3s&x-bitkit-claim=watch-only-account-v1

Enable Paykit UI, then scan or paste the URL. It exercises watch-only consent, normal Pubky authorization, account allocation and xpub export, companion-claim delivery followed by AuthToken delivery, local activation, and Server Accounts management. The xpub can be copied from the account details and used for external address derivation, funding, restart discovery, spending, and tracking disable/re-enable testing.

The request secret is public, so this URL must only be used with a disposable regtest wallet. A Paykit Server listener or receiver harness is still required to verify requester-side relay polling, decryption, and signature verification. A staging deployment is intended once Paykit Server runtime/listener composition is available.

Manual Tests

  • Enable Paykit UI → Settings → Advanced: Server Accounts is visible; disable Paykit UI: it is hidden.
  • Scanner/paste → use the disposable setup request above → approve watch-only sharing → complete normal Pubky authorization: the account becomes active.
  • Copy the xpub from Server Accounts → externally derive a nonzero receive address → send funds → restart Bitkit: the additional-wallet full scan discovers the funds.
  • Spend the externally received funds from Bitkit.
  • Settings → Advanced → Server Accounts → disable and re-enable tracking: the same account and balance return without allocating another account.
  • Missing, duplicate, unknown, or mismatched companion parameters/capability are rejected; ordinary Pubky Auth remains unchanged.

Automated Checks

  • WatchOnlyAccountServiceTests.swift
  • PubkyAuthApprovalSheetTests.swift
  • PubkyAuthRequestTests.swift
  • PaykitReceiverNoiseKeyStoreTests.swift
  • Paykit rc37 simulator build succeeded against LDK Node rc.57.
  • The exact E2E app-build mode (DEBUG E2E_BUILD) succeeds with the notification target's watch-only/localization membership dependencies.
  • The iOS and Android receiver-key tests share an independently verified derivation vector and cover persistence, reuse, invalid caches, and wrong-seed fail-closed behavior.
  • Post-delivery normal-auth failure, retry after delivery, restart retry, activation, and wallet Paykit regressions are covered by the selected suites.
  • 101 selected watch-only, Paykit, Pubky Auth, endpoint, and receiver-key tests passed after rebase.
  • SwiftFormat and git diff --check passed.

@greptile-apps

greptile-appsBot commented Jul 15, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds Paykit Server watch-only accounts. The main changes are:

  • Strict parsing for the watch-only companion claim and capability.
  • Companion authorization with tracked setup and retry states.
  • Monotonic account allocation, backup, restore, and reconciliation.
  • LDK scanning and tracking controls for additional wallet accounts.
  • Deterministic receiver Noise-key derivation and persistence.
  • Settings screens for managing account names and tracking.

Confidence Score: 5/5

This looks safe to merge.

  • The updated delivery-error handling keeps the account tracked after the documented post-delivery authorization failure.
  • No blocking issue remains in the reviewed changes.

Important Files Changed

FilenameOverview
Bitkit/Services/PubkyService.swiftAdds companion authorization and preserves account tracking for the documented post-delivery authorization failure.
Bitkit/Services/WatchOnlyAccountService.swiftAdds account allocation, persistence, authorization lifecycle, backup restoration, and tracking reconciliation.
Bitkit/Services/LightningService.swiftIntegrates watch-only accounts with LDK startup, scanning, address lookahead, and tracking controls.

Reviews (3): Last reviewed commit: "fix: derive Paykit Noise key from wallet..." | Re-trigger Greptile

Comment threadBitkit/Services/PubkyService.swift
@ben-kaufman
ben-kaufman marked this pull request as draft July 15, 2026 21:23
@ben-kaufman
ben-kaufman marked this pull request as ready for review July 16, 2026 15:19
@ben-kaufman
ben-kaufman marked this pull request as draft July 20, 2026 11:18
@ben-kaufman
ben-kaufman marked this pull request as ready for review July 20, 2026 11:48
@piotr-iohk

Copy link
Copy Markdown
Collaborator

Same review notes as the companion Android PR: synonymdev/bitkit-android#1084 (comment)

(Please apply the same UI gating / testing / ADR questions there.)

CI / merge

  • Merge conflicts with the base branch (codex/paykit-rc33-receivers) — please rebase/resolve.
  • E2E build-local / build-staging currently fail: LightningService references WatchOnlyAccountRecord from the BitkitNotification target where that type isn’t in scope. Please fix so CI builds again.

@ben-kaufman
ben-kaufmanforce-pushed the codex/paykit-watch-only-accounts branch from 67d11d0 to 3d5c9beCompareJuly 22, 2026 10:15
Base automatically changed from codex/paykit-rc33-receivers to masterJuly 22, 2026 11:10
@ben-kaufman
ben-kaufmanforce-pushed the codex/paykit-watch-only-accounts branch from 990db23 to e4a5c92CompareJuly 22, 2026 11:39
@ben-kaufmanChatGPT Codex Connector

Copy link
Copy Markdown
ContributorAuthor

Thanks — the corresponding iOS items are now addressed on the latest commit.

UI gating

Settings → Advanced → Server Accounts is now only visible when Enable Paykit UI is enabled.

Testing the flow

Reviewers can exercise the Bitkit-side flow using this disposable request:

pubkyauth://signin?caps=/pub/paykit/v0/bitkit/server/:rw&relay=https://httprelay.pubky.app/inbox/&secret=e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3s&x-bitkit-claim=watch-only-account-v1

Enable Paykit UI, then paste or scan it. This exercises the watch-only consent, normal Pubky authorization, account allocation/xpub export, both relay deliveries, local activation, and Server Accounts UI.

The xpub can then be copied from the account details sheet and used to test external address derivation, funding, restart discovery, spending, and disabling/re-enabling tracking. Because the request secret is public, this should only be done with a disposable regtest wallet.

The missing part is requester-side polling, decryption, and signature verification; that requires a Paykit Server listener or receiver harness.

Server staging

The intended next server-side QA step is to deploy Paykit Server on staging once runtime/listener composition exists. There is no staging endpoint yet, and that work is outside these mobile PRs.

Retry and activation behavior

The same post-delivery behavior is covered on iOS:

  • Paykit's documented post-delivery AuthorizationFailure leaves the account authorizing and tracked.
  • Retries and retries after restart reuse the same account index, xpub, and payload.
  • Pre-delivery failures return newly pending accounts to an unloaded state.
  • Activation is protected from caller cancellation.
  • A local activation failure after remote delivery is recovered by retrying the same combined authorization and activating the same account.

Protocol version

ADR 0001's watch-only-account-v1 is the currently agreed protocol and is intentionally implemented here. ADR 0002's proposed watch-only-account-service-v1 delegation flow has not been agreed and is not part of the current implementation.

CI and regression

The branch was rebased and is currently mergeable. The notification-target failure was fixed through the target membership exclusion list; build-local and build-staging now pass. The selected 101 watch-only, Pubky Auth, endpoint, receiver-key, and wallet Paykit regression tests pass.

The existing pubky_paykit E2E fixture issue uses outdated pre-made profiles and reproduces on the base PR, so it is inherited CI noise rather than a regression from this watch-only change.

@piotr-iohk

Copy link
Copy Markdown
Collaborator

Same retest notes as the companion Android PR: synonymdev/bitkit-android#1084 (comment)

iOS-specific bits already covered there: Enter-manually gap for pubkyauth, timeout → Incomplete setup → retry OBS, and the icon/title/copy parity nits.

@ovitrif
ovitrif requested a review from piotr-iohkJuly 23, 2026 20:04
@piotr-iohk

Copy link
Copy Markdown
Collaborator

Retested after the alignment fixes. Looks good to approve on my side.

Verified:

  • Settings row icon / title casing aligned
  • Server Accounts header burger present
  • pubkyauth limited to main scan (Send paste / Enter manually reject as intended)
  • Disposable watch-only setup URL → consent → authorize → Server Accounts still works

No remaining iOS blockers from this pass. Android still has a home-Scan gating bug (home Scan is modeled as Send, so pubkyauth is rejected there); noted on the companion PR.

@jvsena42jvsena42 added this to the 2.5.0 milestone Jul 24, 2026

@jvsena42jvsena42 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Simulator.Screen.Recording.-.iPhone.17.-.2026-07-24.at.10.05.22.mov

Approved with some nits

Comment threadBitkit/Services/PubkyService.swift
Comment threadBitkit/Managers/ScannerManager.swift
@jvsena42
jvsena42 merged commit 95c1766 into masterJul 24, 2026
11 checks passed
@jvsena42
jvsena42 deleted the codex/paykit-watch-only-accounts branch July 24, 2026 13:48
@jvsena42jvsena42 mentioned this pull request Jul 27, 2026
13 tasks
Sign up for freeto 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.

4 participants

@ben-kaufman@piotr-iohk@jvsena42@MrxiaoAxiao
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat: add Paykit server accounts - #630

Merged
jvsena42 merged 19 commits into
masterfrom
codex/paykit-watch-only-accounts
Jul 24, 2026
Merged

feat: add Paykit server accounts#630
jvsena42 merged 19 commits into
masterfrom
codex/paykit-watch-only-accounts

Conversation

@ben-kaufman

@ben-kaufmanben-kaufman commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Description

This PR builds on #620 to add Paykit Server watch-only accounts:

  • Presents a dedicated watch-only consent step before the normal Pubky authorization screen.
  • Requires x-bitkit-claim=watch-only-account-v1 and the exact /pub/paykit/v0/bitkit/server/:rw capability, rejecting missing, unknown, duplicate, or mismatched companion parameters.
  • Uses Paykit companion authorization and fails closed if companion delivery or normal authorization fails.
  • Allocates monotonic BIP84 accounts, reuses the same incomplete account on retry, and exports the exact versioned 84-byte claim payload.
  • Automatically names new accounts from the requesting service; users can rename them later from Settings.
  • Hides Settings → Advanced → Server Accounts until the experimental Paykit UI is enabled.
  • Persists and backs up account metadata, serializes lifecycle and reconciliation changes, and supports disabling or restoring LDK tracking without deleting wallet state.
  • Configures additional-wallet scanning and the rolling 1,000-address lookahead.
  • Deterministically derives the receiver Noise key from the wallet's BIP39 seed using domain-separated HKDF-SHA256 with version, network, and receiver-path context; securely caches and verifies it so the wallet seed remains the only backup required.
  • Reuses the derived Noise key across authentication and restarts, and publishes only its receiver marker when live session access becomes available.
  • Updates to Paykit 0.1.0-rc37, LDK Node 0.7.0-rc.57, and Bitkit Core 0.4.2; Bitkit Core now supplies trusted extended-public-key serialization, so Base58Swift and its BigInt dependency are no longer required.

The relay claim contains only the versioned account index, address type, and serialized xpub. Local names and tracking state are not disclosed. Ordinary Pubky Auth remains unchanged.

References:

QA Notes

This slice implements ADR 0001's watch-only-account-v1 claim, which is the currently agreed protocol. ADR 0002's proposed watch-only-account-service-v1 delegation flow has not been agreed and is not part of the current implementation.

Reviewers can exercise the complete Bitkit-side setup and account lifecycle with this disposable test request:

pubkyauth://signin?caps=/pub/paykit/v0/bitkit/server/:rw&relay=https://httprelay.pubky.app/inbox/&secret=e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3s&x-bitkit-claim=watch-only-account-v1

Enable Paykit UI, then scan or paste the URL. It exercises watch-only consent, normal Pubky authorization, account allocation and xpub export, companion-claim delivery followed by AuthToken delivery, local activation, and Server Accounts management. The xpub can be copied from the account details and used for external address derivation, funding, restart discovery, spending, and tracking disable/re-enable testing.

The request secret is public, so this URL must only be used with a disposable regtest wallet. A Paykit Server listener or receiver harness is still required to verify requester-side relay polling, decryption, and signature verification. A staging deployment is intended once Paykit Server runtime/listener composition is available.

Manual Tests

  • Enable Paykit UI → Settings → Advanced: Server Accounts is visible; disable Paykit UI: it is hidden.
  • Scanner/paste → use the disposable setup request above → approve watch-only sharing → complete normal Pubky authorization: the account becomes active.
  • Copy the xpub from Server Accounts → externally derive a nonzero receive address → send funds → restart Bitkit: the additional-wallet full scan discovers the funds.
  • Spend the externally received funds from Bitkit.
  • Settings → Advanced → Server Accounts → disable and re-enable tracking: the same account and balance return without allocating another account.
  • Missing, duplicate, unknown, or mismatched companion parameters/capability are rejected; ordinary Pubky Auth remains unchanged.

Automated Checks

  • WatchOnlyAccountServiceTests.swift
  • PubkyAuthApprovalSheetTests.swift
  • PubkyAuthRequestTests.swift
  • PaykitReceiverNoiseKeyStoreTests.swift
  • Paykit rc37 simulator build succeeded against LDK Node rc.57.
  • The exact E2E app-build mode (DEBUG E2E_BUILD) succeeds with the notification target's watch-only/localization membership dependencies.
  • The iOS and Android receiver-key tests share an independently verified derivation vector and cover persistence, reuse, invalid caches, and wrong-seed fail-closed behavior.
  • Post-delivery normal-auth failure, retry after delivery, restart retry, activation, and wallet Paykit regressions are covered by the selected suites.
  • 101 selected watch-only, Paykit, Pubky Auth, endpoint, and receiver-key tests passed after rebase.
  • SwiftFormat and git diff --check passed.

@greptile-apps

greptile-appsBot commented Jul 15, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds Paykit Server watch-only accounts. The main changes are:

  • Strict parsing for the watch-only companion claim and capability.
  • Companion authorization with tracked setup and retry states.
  • Monotonic account allocation, backup, restore, and reconciliation.
  • LDK scanning and tracking controls for additional wallet accounts.
  • Deterministic receiver Noise-key derivation and persistence.
  • Settings screens for managing account names and tracking.

Confidence Score: 5/5

This looks safe to merge.

  • The updated delivery-error handling keeps the account tracked after the documented post-delivery authorization failure.
  • No blocking issue remains in the reviewed changes.

Important Files Changed

FilenameOverview
Bitkit/Services/PubkyService.swiftAdds companion authorization and preserves account tracking for the documented post-delivery authorization failure.
Bitkit/Services/WatchOnlyAccountService.swiftAdds account allocation, persistence, authorization lifecycle, backup restoration, and tracking reconciliation.
Bitkit/Services/LightningService.swiftIntegrates watch-only accounts with LDK startup, scanning, address lookahead, and tracking controls.

Reviews (3): Last reviewed commit: "fix: derive Paykit Noise key from wallet..." | Re-trigger Greptile

Comment threadBitkit/Services/PubkyService.swift
@ben-kaufman
ben-kaufman marked this pull request as draft July 15, 2026 21:23
@ben-kaufman
ben-kaufman marked this pull request as ready for review July 16, 2026 15:19
@ben-kaufman
ben-kaufman marked this pull request as draft July 20, 2026 11:18
@ben-kaufman
ben-kaufman marked this pull request as ready for review July 20, 2026 11:48
@piotr-iohk

Copy link
Copy Markdown
Collaborator

Same review notes as the companion Android PR: synonymdev/bitkit-android#1084 (comment)

(Please apply the same UI gating / testing / ADR questions there.)

CI / merge

  • Merge conflicts with the base branch (codex/paykit-rc33-receivers) — please rebase/resolve.
  • E2E build-local / build-staging currently fail: LightningService references WatchOnlyAccountRecord from the BitkitNotification target where that type isn’t in scope. Please fix so CI builds again.

@ben-kaufman
ben-kaufmanforce-pushed the codex/paykit-watch-only-accounts branch from 67d11d0 to 3d5c9beCompareJuly 22, 2026 10:15
Base automatically changed from codex/paykit-rc33-receivers to masterJuly 22, 2026 11:10
@ben-kaufman
ben-kaufmanforce-pushed the codex/paykit-watch-only-accounts branch from 990db23 to e4a5c92CompareJuly 22, 2026 11:39
@ben-kaufmanChatGPT Codex Connector

Copy link
Copy Markdown
ContributorAuthor

Thanks — the corresponding iOS items are now addressed on the latest commit.

UI gating

Settings → Advanced → Server Accounts is now only visible when Enable Paykit UI is enabled.

Testing the flow

Reviewers can exercise the Bitkit-side flow using this disposable request:

pubkyauth://signin?caps=/pub/paykit/v0/bitkit/server/:rw&relay=https://httprelay.pubky.app/inbox/&secret=e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3s&x-bitkit-claim=watch-only-account-v1

Enable Paykit UI, then paste or scan it. This exercises the watch-only consent, normal Pubky authorization, account allocation/xpub export, both relay deliveries, local activation, and Server Accounts UI.

The xpub can then be copied from the account details sheet and used to test external address derivation, funding, restart discovery, spending, and disabling/re-enabling tracking. Because the request secret is public, this should only be done with a disposable regtest wallet.

The missing part is requester-side polling, decryption, and signature verification; that requires a Paykit Server listener or receiver harness.

Server staging

The intended next server-side QA step is to deploy Paykit Server on staging once runtime/listener composition exists. There is no staging endpoint yet, and that work is outside these mobile PRs.

Retry and activation behavior

The same post-delivery behavior is covered on iOS:

  • Paykit's documented post-delivery AuthorizationFailure leaves the account authorizing and tracked.
  • Retries and retries after restart reuse the same account index, xpub, and payload.
  • Pre-delivery failures return newly pending accounts to an unloaded state.
  • Activation is protected from caller cancellation.
  • A local activation failure after remote delivery is recovered by retrying the same combined authorization and activating the same account.

Protocol version

ADR 0001's watch-only-account-v1 is the currently agreed protocol and is intentionally implemented here. ADR 0002's proposed watch-only-account-service-v1 delegation flow has not been agreed and is not part of the current implementation.

CI and regression

The branch was rebased and is currently mergeable. The notification-target failure was fixed through the target membership exclusion list; build-local and build-staging now pass. The selected 101 watch-only, Pubky Auth, endpoint, receiver-key, and wallet Paykit regression tests pass.

The existing pubky_paykit E2E fixture issue uses outdated pre-made profiles and reproduces on the base PR, so it is inherited CI noise rather than a regression from this watch-only change.

@piotr-iohk

Copy link
Copy Markdown
Collaborator

Same retest notes as the companion Android PR: synonymdev/bitkit-android#1084 (comment)

iOS-specific bits already covered there: Enter-manually gap for pubkyauth, timeout → Incomplete setup → retry OBS, and the icon/title/copy parity nits.

@ovitrif
ovitrif requested a review from piotr-iohkJuly 23, 2026 20:04
@piotr-iohk

Copy link
Copy Markdown
Collaborator

Retested after the alignment fixes. Looks good to approve on my side.

Verified:

  • Settings row icon / title casing aligned
  • Server Accounts header burger present
  • pubkyauth limited to main scan (Send paste / Enter manually reject as intended)
  • Disposable watch-only setup URL → consent → authorize → Server Accounts still works

No remaining iOS blockers from this pass. Android still has a home-Scan gating bug (home Scan is modeled as Send, so pubkyauth is rejected there); noted on the companion PR.

@jvsena42jvsena42 added this to the 2.5.0 milestone Jul 24, 2026

@jvsena42jvsena42 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Simulator.Screen.Recording.-.iPhone.17.-.2026-07-24.at.10.05.22.mov

Approved with some nits

Comment threadBitkit/Services/PubkyService.swift
Comment threadBitkit/Managers/ScannerManager.swift
@jvsena42
jvsena42 merged commit 95c1766 into masterJul 24, 2026
11 checks passed
@jvsena42
jvsena42 deleted the codex/paykit-watch-only-accounts branch July 24, 2026 13:48
@jvsena42jvsena42 mentioned this pull request Jul 27, 2026
13 tasks
Sign up for freeto 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.

4 participants

@ben-kaufman@piotr-iohk@jvsena42@MrxiaoAxiao
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat: add Paykit server accounts - #630

Merged
jvsena42 merged 19 commits into
masterfrom
codex/paykit-watch-only-accounts
Jul 24, 2026
Merged

feat: add Paykit server accounts#630
jvsena42 merged 19 commits into
masterfrom
codex/paykit-watch-only-accounts

Conversation

@ben-kaufman

@ben-kaufmanben-kaufman commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Description

This PR builds on #620 to add Paykit Server watch-only accounts:

  • Presents a dedicated watch-only consent step before the normal Pubky authorization screen.
  • Requires x-bitkit-claim=watch-only-account-v1 and the exact /pub/paykit/v0/bitkit/server/:rw capability, rejecting missing, unknown, duplicate, or mismatched companion parameters.
  • Uses Paykit companion authorization and fails closed if companion delivery or normal authorization fails.
  • Allocates monotonic BIP84 accounts, reuses the same incomplete account on retry, and exports the exact versioned 84-byte claim payload.
  • Automatically names new accounts from the requesting service; users can rename them later from Settings.
  • Hides Settings → Advanced → Server Accounts until the experimental Paykit UI is enabled.
  • Persists and backs up account metadata, serializes lifecycle and reconciliation changes, and supports disabling or restoring LDK tracking without deleting wallet state.
  • Configures additional-wallet scanning and the rolling 1,000-address lookahead.
  • Deterministically derives the receiver Noise key from the wallet's BIP39 seed using domain-separated HKDF-SHA256 with version, network, and receiver-path context; securely caches and verifies it so the wallet seed remains the only backup required.
  • Reuses the derived Noise key across authentication and restarts, and publishes only its receiver marker when live session access becomes available.
  • Updates to Paykit 0.1.0-rc37, LDK Node 0.7.0-rc.57, and Bitkit Core 0.4.2; Bitkit Core now supplies trusted extended-public-key serialization, so Base58Swift and its BigInt dependency are no longer required.

The relay claim contains only the versioned account index, address type, and serialized xpub. Local names and tracking state are not disclosed. Ordinary Pubky Auth remains unchanged.

References:

QA Notes

This slice implements ADR 0001's watch-only-account-v1 claim, which is the currently agreed protocol. ADR 0002's proposed watch-only-account-service-v1 delegation flow has not been agreed and is not part of the current implementation.

Reviewers can exercise the complete Bitkit-side setup and account lifecycle with this disposable test request:

pubkyauth://signin?caps=/pub/paykit/v0/bitkit/server/:rw&relay=https://httprelay.pubky.app/inbox/&secret=e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3s&x-bitkit-claim=watch-only-account-v1

Enable Paykit UI, then scan or paste the URL. It exercises watch-only consent, normal Pubky authorization, account allocation and xpub export, companion-claim delivery followed by AuthToken delivery, local activation, and Server Accounts management. The xpub can be copied from the account details and used for external address derivation, funding, restart discovery, spending, and tracking disable/re-enable testing.

The request secret is public, so this URL must only be used with a disposable regtest wallet. A Paykit Server listener or receiver harness is still required to verify requester-side relay polling, decryption, and signature verification. A staging deployment is intended once Paykit Server runtime/listener composition is available.

Manual Tests

  • Enable Paykit UI → Settings → Advanced: Server Accounts is visible; disable Paykit UI: it is hidden.
  • Scanner/paste → use the disposable setup request above → approve watch-only sharing → complete normal Pubky authorization: the account becomes active.
  • Copy the xpub from Server Accounts → externally derive a nonzero receive address → send funds → restart Bitkit: the additional-wallet full scan discovers the funds.
  • Spend the externally received funds from Bitkit.
  • Settings → Advanced → Server Accounts → disable and re-enable tracking: the same account and balance return without allocating another account.
  • Missing, duplicate, unknown, or mismatched companion parameters/capability are rejected; ordinary Pubky Auth remains unchanged.

Automated Checks

  • WatchOnlyAccountServiceTests.swift
  • PubkyAuthApprovalSheetTests.swift
  • PubkyAuthRequestTests.swift
  • PaykitReceiverNoiseKeyStoreTests.swift
  • Paykit rc37 simulator build succeeded against LDK Node rc.57.
  • The exact E2E app-build mode (DEBUG E2E_BUILD) succeeds with the notification target's watch-only/localization membership dependencies.
  • The iOS and Android receiver-key tests share an independently verified derivation vector and cover persistence, reuse, invalid caches, and wrong-seed fail-closed behavior.
  • Post-delivery normal-auth failure, retry after delivery, restart retry, activation, and wallet Paykit regressions are covered by the selected suites.
  • 101 selected watch-only, Paykit, Pubky Auth, endpoint, and receiver-key tests passed after rebase.
  • SwiftFormat and git diff --check passed.

@greptile-apps

greptile-appsBot commented Jul 15, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds Paykit Server watch-only accounts. The main changes are:

  • Strict parsing for the watch-only companion claim and capability.
  • Companion authorization with tracked setup and retry states.
  • Monotonic account allocation, backup, restore, and reconciliation.
  • LDK scanning and tracking controls for additional wallet accounts.
  • Deterministic receiver Noise-key derivation and persistence.
  • Settings screens for managing account names and tracking.

Confidence Score: 5/5

This looks safe to merge.

  • The updated delivery-error handling keeps the account tracked after the documented post-delivery authorization failure.
  • No blocking issue remains in the reviewed changes.

Important Files Changed

FilenameOverview
Bitkit/Services/PubkyService.swiftAdds companion authorization and preserves account tracking for the documented post-delivery authorization failure.
Bitkit/Services/WatchOnlyAccountService.swiftAdds account allocation, persistence, authorization lifecycle, backup restoration, and tracking reconciliation.
Bitkit/Services/LightningService.swiftIntegrates watch-only accounts with LDK startup, scanning, address lookahead, and tracking controls.

Reviews (3): Last reviewed commit: "fix: derive Paykit Noise key from wallet..." | Re-trigger Greptile

Comment threadBitkit/Services/PubkyService.swift
@ben-kaufman
ben-kaufman marked this pull request as draft July 15, 2026 21:23
@ben-kaufman
ben-kaufman marked this pull request as ready for review July 16, 2026 15:19
@ben-kaufman
ben-kaufman marked this pull request as draft July 20, 2026 11:18
@ben-kaufman
ben-kaufman marked this pull request as ready for review July 20, 2026 11:48
@piotr-iohk

Copy link
Copy Markdown
Collaborator

Same review notes as the companion Android PR: synonymdev/bitkit-android#1084 (comment)

(Please apply the same UI gating / testing / ADR questions there.)

CI / merge

  • Merge conflicts with the base branch (codex/paykit-rc33-receivers) — please rebase/resolve.
  • E2E build-local / build-staging currently fail: LightningService references WatchOnlyAccountRecord from the BitkitNotification target where that type isn’t in scope. Please fix so CI builds again.

@ben-kaufman
ben-kaufmanforce-pushed the codex/paykit-watch-only-accounts branch from 67d11d0 to 3d5c9beCompareJuly 22, 2026 10:15
Base automatically changed from codex/paykit-rc33-receivers to masterJuly 22, 2026 11:10
@ben-kaufman
ben-kaufmanforce-pushed the codex/paykit-watch-only-accounts branch from 990db23 to e4a5c92CompareJuly 22, 2026 11:39
@ben-kaufmanChatGPT Codex Connector

Copy link
Copy Markdown
ContributorAuthor

Thanks — the corresponding iOS items are now addressed on the latest commit.

UI gating

Settings → Advanced → Server Accounts is now only visible when Enable Paykit UI is enabled.

Testing the flow

Reviewers can exercise the Bitkit-side flow using this disposable request:

pubkyauth://signin?caps=/pub/paykit/v0/bitkit/server/:rw&relay=https://httprelay.pubky.app/inbox/&secret=e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3s&x-bitkit-claim=watch-only-account-v1

Enable Paykit UI, then paste or scan it. This exercises the watch-only consent, normal Pubky authorization, account allocation/xpub export, both relay deliveries, local activation, and Server Accounts UI.

The xpub can then be copied from the account details sheet and used to test external address derivation, funding, restart discovery, spending, and disabling/re-enabling tracking. Because the request secret is public, this should only be done with a disposable regtest wallet.

The missing part is requester-side polling, decryption, and signature verification; that requires a Paykit Server listener or receiver harness.

Server staging

The intended next server-side QA step is to deploy Paykit Server on staging once runtime/listener composition exists. There is no staging endpoint yet, and that work is outside these mobile PRs.

Retry and activation behavior

The same post-delivery behavior is covered on iOS:

  • Paykit's documented post-delivery AuthorizationFailure leaves the account authorizing and tracked.
  • Retries and retries after restart reuse the same account index, xpub, and payload.
  • Pre-delivery failures return newly pending accounts to an unloaded state.
  • Activation is protected from caller cancellation.
  • A local activation failure after remote delivery is recovered by retrying the same combined authorization and activating the same account.

Protocol version

ADR 0001's watch-only-account-v1 is the currently agreed protocol and is intentionally implemented here. ADR 0002's proposed watch-only-account-service-v1 delegation flow has not been agreed and is not part of the current implementation.

CI and regression

The branch was rebased and is currently mergeable. The notification-target failure was fixed through the target membership exclusion list; build-local and build-staging now pass. The selected 101 watch-only, Pubky Auth, endpoint, receiver-key, and wallet Paykit regression tests pass.

The existing pubky_paykit E2E fixture issue uses outdated pre-made profiles and reproduces on the base PR, so it is inherited CI noise rather than a regression from this watch-only change.

@piotr-iohk

Copy link
Copy Markdown
Collaborator

Same retest notes as the companion Android PR: synonymdev/bitkit-android#1084 (comment)

iOS-specific bits already covered there: Enter-manually gap for pubkyauth, timeout → Incomplete setup → retry OBS, and the icon/title/copy parity nits.

@ovitrif
ovitrif requested a review from piotr-iohkJuly 23, 2026 20:04
@piotr-iohk

Copy link
Copy Markdown
Collaborator

Retested after the alignment fixes. Looks good to approve on my side.

Verified:

  • Settings row icon / title casing aligned
  • Server Accounts header burger present
  • pubkyauth limited to main scan (Send paste / Enter manually reject as intended)
  • Disposable watch-only setup URL → consent → authorize → Server Accounts still works

No remaining iOS blockers from this pass. Android still has a home-Scan gating bug (home Scan is modeled as Send, so pubkyauth is rejected there); noted on the companion PR.

@jvsena42jvsena42 added this to the 2.5.0 milestone Jul 24, 2026

@jvsena42jvsena42 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Simulator.Screen.Recording.-.iPhone.17.-.2026-07-24.at.10.05.22.mov

Approved with some nits

Comment threadBitkit/Services/PubkyService.swift
Comment threadBitkit/Managers/ScannerManager.swift
@jvsena42
jvsena42 merged commit 95c1766 into masterJul 24, 2026
11 checks passed
@jvsena42
jvsena42 deleted the codex/paykit-watch-only-accounts branch July 24, 2026 13:48
@jvsena42jvsena42 mentioned this pull request Jul 27, 2026
13 tasks
Sign up for freeto 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.

4 participants

@ben-kaufman@piotr-iohk@jvsena42@MrxiaoAxiao
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

feat: add Paykit server accounts - #630

Merged
jvsena42 merged 19 commits into
masterfrom
codex/paykit-watch-only-accounts
Jul 24, 2026
Merged

feat: add Paykit server accounts#630
jvsena42 merged 19 commits into
masterfrom
codex/paykit-watch-only-accounts

Conversation

@ben-kaufman

@ben-kaufmanben-kaufman commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Description

This PR builds on #620 to add Paykit Server watch-only accounts:

  • Presents a dedicated watch-only consent step before the normal Pubky authorization screen.
  • Requires x-bitkit-claim=watch-only-account-v1 and the exact /pub/paykit/v0/bitkit/server/:rw capability, rejecting missing, unknown, duplicate, or mismatched companion parameters.
  • Uses Paykit companion authorization and fails closed if companion delivery or normal authorization fails.
  • Allocates monotonic BIP84 accounts, reuses the same incomplete account on retry, and exports the exact versioned 84-byte claim payload.
  • Automatically names new accounts from the requesting service; users can rename them later from Settings.
  • Hides Settings → Advanced → Server Accounts until the experimental Paykit UI is enabled.
  • Persists and backs up account metadata, serializes lifecycle and reconciliation changes, and supports disabling or restoring LDK tracking without deleting wallet state.
  • Configures additional-wallet scanning and the rolling 1,000-address lookahead.
  • Deterministically derives the receiver Noise key from the wallet's BIP39 seed using domain-separated HKDF-SHA256 with version, network, and receiver-path context; securely caches and verifies it so the wallet seed remains the only backup required.
  • Reuses the derived Noise key across authentication and restarts, and publishes only its receiver marker when live session access becomes available.
  • Updates to Paykit 0.1.0-rc37, LDK Node 0.7.0-rc.57, and Bitkit Core 0.4.2; Bitkit Core now supplies trusted extended-public-key serialization, so Base58Swift and its BigInt dependency are no longer required.

The relay claim contains only the versioned account index, address type, and serialized xpub. Local names and tracking state are not disclosed. Ordinary Pubky Auth remains unchanged.

References:

QA Notes

This slice implements ADR 0001's watch-only-account-v1 claim, which is the currently agreed protocol. ADR 0002's proposed watch-only-account-service-v1 delegation flow has not been agreed and is not part of the current implementation.

Reviewers can exercise the complete Bitkit-side setup and account lifecycle with this disposable test request:

pubkyauth://signin?caps=/pub/paykit/v0/bitkit/server/:rw&relay=https://httprelay.pubky.app/inbox/&secret=e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3s&x-bitkit-claim=watch-only-account-v1

Enable Paykit UI, then scan or paste the URL. It exercises watch-only consent, normal Pubky authorization, account allocation and xpub export, companion-claim delivery followed by AuthToken delivery, local activation, and Server Accounts management. The xpub can be copied from the account details and used for external address derivation, funding, restart discovery, spending, and tracking disable/re-enable testing.

The request secret is public, so this URL must only be used with a disposable regtest wallet. A Paykit Server listener or receiver harness is still required to verify requester-side relay polling, decryption, and signature verification. A staging deployment is intended once Paykit Server runtime/listener composition is available.

Manual Tests

  • Enable Paykit UI → Settings → Advanced: Server Accounts is visible; disable Paykit UI: it is hidden.
  • Scanner/paste → use the disposable setup request above → approve watch-only sharing → complete normal Pubky authorization: the account becomes active.
  • Copy the xpub from Server Accounts → externally derive a nonzero receive address → send funds → restart Bitkit: the additional-wallet full scan discovers the funds.
  • Spend the externally received funds from Bitkit.
  • Settings → Advanced → Server Accounts → disable and re-enable tracking: the same account and balance return without allocating another account.
  • Missing, duplicate, unknown, or mismatched companion parameters/capability are rejected; ordinary Pubky Auth remains unchanged.

Automated Checks

  • WatchOnlyAccountServiceTests.swift
  • PubkyAuthApprovalSheetTests.swift
  • PubkyAuthRequestTests.swift
  • PaykitReceiverNoiseKeyStoreTests.swift
  • Paykit rc37 simulator build succeeded against LDK Node rc.57.
  • The exact E2E app-build mode (DEBUG E2E_BUILD) succeeds with the notification target's watch-only/localization membership dependencies.
  • The iOS and Android receiver-key tests share an independently verified derivation vector and cover persistence, reuse, invalid caches, and wrong-seed fail-closed behavior.
  • Post-delivery normal-auth failure, retry after delivery, restart retry, activation, and wallet Paykit regressions are covered by the selected suites.
  • 101 selected watch-only, Paykit, Pubky Auth, endpoint, and receiver-key tests passed after rebase.
  • SwiftFormat and git diff --check passed.

@greptile-apps

greptile-appsBot commented Jul 15, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds Paykit Server watch-only accounts. The main changes are:

  • Strict parsing for the watch-only companion claim and capability.
  • Companion authorization with tracked setup and retry states.
  • Monotonic account allocation, backup, restore, and reconciliation.
  • LDK scanning and tracking controls for additional wallet accounts.
  • Deterministic receiver Noise-key derivation and persistence.
  • Settings screens for managing account names and tracking.

Confidence Score: 5/5

This looks safe to merge.

  • The updated delivery-error handling keeps the account tracked after the documented post-delivery authorization failure.
  • No blocking issue remains in the reviewed changes.

Important Files Changed

FilenameOverview
Bitkit/Services/PubkyService.swiftAdds companion authorization and preserves account tracking for the documented post-delivery authorization failure.
Bitkit/Services/WatchOnlyAccountService.swiftAdds account allocation, persistence, authorization lifecycle, backup restoration, and tracking reconciliation.
Bitkit/Services/LightningService.swiftIntegrates watch-only accounts with LDK startup, scanning, address lookahead, and tracking controls.

Reviews (3): Last reviewed commit: "fix: derive Paykit Noise key from wallet..." | Re-trigger Greptile

Comment threadBitkit/Services/PubkyService.swift
@ben-kaufman
ben-kaufman marked this pull request as draft July 15, 2026 21:23
@ben-kaufman
ben-kaufman marked this pull request as ready for review July 16, 2026 15:19
@ben-kaufman
ben-kaufman marked this pull request as draft July 20, 2026 11:18
@ben-kaufman
ben-kaufman marked this pull request as ready for review July 20, 2026 11:48
@piotr-iohk

Copy link
Copy Markdown
Collaborator

Same review notes as the companion Android PR: synonymdev/bitkit-android#1084 (comment)

(Please apply the same UI gating / testing / ADR questions there.)

CI / merge

  • Merge conflicts with the base branch (codex/paykit-rc33-receivers) — please rebase/resolve.
  • E2E build-local / build-staging currently fail: LightningService references WatchOnlyAccountRecord from the BitkitNotification target where that type isn’t in scope. Please fix so CI builds again.

@ben-kaufman
ben-kaufmanforce-pushed the codex/paykit-watch-only-accounts branch from 67d11d0 to 3d5c9beCompareJuly 22, 2026 10:15
Base automatically changed from codex/paykit-rc33-receivers to masterJuly 22, 2026 11:10
@ben-kaufman
ben-kaufmanforce-pushed the codex/paykit-watch-only-accounts branch from 990db23 to e4a5c92CompareJuly 22, 2026 11:39
@ben-kaufmanChatGPT Codex Connector

Copy link
Copy Markdown
ContributorAuthor

Thanks — the corresponding iOS items are now addressed on the latest commit.

UI gating

Settings → Advanced → Server Accounts is now only visible when Enable Paykit UI is enabled.

Testing the flow

Reviewers can exercise the Bitkit-side flow using this disposable request:

pubkyauth://signin?caps=/pub/paykit/v0/bitkit/server/:rw&relay=https://httprelay.pubky.app/inbox/&secret=e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3s&x-bitkit-claim=watch-only-account-v1

Enable Paykit UI, then paste or scan it. This exercises the watch-only consent, normal Pubky authorization, account allocation/xpub export, both relay deliveries, local activation, and Server Accounts UI.

The xpub can then be copied from the account details sheet and used to test external address derivation, funding, restart discovery, spending, and disabling/re-enabling tracking. Because the request secret is public, this should only be done with a disposable regtest wallet.

The missing part is requester-side polling, decryption, and signature verification; that requires a Paykit Server listener or receiver harness.

Server staging

The intended next server-side QA step is to deploy Paykit Server on staging once runtime/listener composition exists. There is no staging endpoint yet, and that work is outside these mobile PRs.

Retry and activation behavior

The same post-delivery behavior is covered on iOS:

  • Paykit's documented post-delivery AuthorizationFailure leaves the account authorizing and tracked.
  • Retries and retries after restart reuse the same account index, xpub, and payload.
  • Pre-delivery failures return newly pending accounts to an unloaded state.
  • Activation is protected from caller cancellation.
  • A local activation failure after remote delivery is recovered by retrying the same combined authorization and activating the same account.

Protocol version

ADR 0001's watch-only-account-v1 is the currently agreed protocol and is intentionally implemented here. ADR 0002's proposed watch-only-account-service-v1 delegation flow has not been agreed and is not part of the current implementation.

CI and regression

The branch was rebased and is currently mergeable. The notification-target failure was fixed through the target membership exclusion list; build-local and build-staging now pass. The selected 101 watch-only, Pubky Auth, endpoint, receiver-key, and wallet Paykit regression tests pass.

The existing pubky_paykit E2E fixture issue uses outdated pre-made profiles and reproduces on the base PR, so it is inherited CI noise rather than a regression from this watch-only change.

@piotr-iohk

Copy link
Copy Markdown
Collaborator

Same retest notes as the companion Android PR: synonymdev/bitkit-android#1084 (comment)

iOS-specific bits already covered there: Enter-manually gap for pubkyauth, timeout → Incomplete setup → retry OBS, and the icon/title/copy parity nits.

@ovitrif
ovitrif requested a review from piotr-iohkJuly 23, 2026 20:04
@piotr-iohk

Copy link
Copy Markdown
Collaborator

Retested after the alignment fixes. Looks good to approve on my side.

Verified:

  • Settings row icon / title casing aligned
  • Server Accounts header burger present
  • pubkyauth limited to main scan (Send paste / Enter manually reject as intended)
  • Disposable watch-only setup URL → consent → authorize → Server Accounts still works

No remaining iOS blockers from this pass. Android still has a home-Scan gating bug (home Scan is modeled as Send, so pubkyauth is rejected there); noted on the companion PR.

@jvsena42jvsena42 added this to the 2.5.0 milestone Jul 24, 2026

@jvsena42jvsena42 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Simulator.Screen.Recording.-.iPhone.17.-.2026-07-24.at.10.05.22.mov

Approved with some nits

Comment threadBitkit/Services/PubkyService.swift
Comment threadBitkit/Managers/ScannerManager.swift
@jvsena42
jvsena42 merged commit 95c1766 into masterJul 24, 2026
11 checks passed
@jvsena42
jvsena42 deleted the codex/paykit-watch-only-accounts branch July 24, 2026 13:48
@jvsena42jvsena42 mentioned this pull request Jul 27, 2026
13 tasks
Sign up for freeto 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.

4 participants

@ben-kaufman@piotr-iohk@jvsena42@MrxiaoAxiao
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat: add Paykit server accounts - #630

Merged
jvsena42 merged 19 commits into
masterfrom
codex/paykit-watch-only-accounts
Jul 24, 2026
Merged

feat: add Paykit server accounts#630
jvsena42 merged 19 commits into
masterfrom
codex/paykit-watch-only-accounts

Conversation

@ben-kaufman

@ben-kaufmanben-kaufman commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Description

This PR builds on #620 to add Paykit Server watch-only accounts:

  • Presents a dedicated watch-only consent step before the normal Pubky authorization screen.
  • Requires x-bitkit-claim=watch-only-account-v1 and the exact /pub/paykit/v0/bitkit/server/:rw capability, rejecting missing, unknown, duplicate, or mismatched companion parameters.
  • Uses Paykit companion authorization and fails closed if companion delivery or normal authorization fails.
  • Allocates monotonic BIP84 accounts, reuses the same incomplete account on retry, and exports the exact versioned 84-byte claim payload.
  • Automatically names new accounts from the requesting service; users can rename them later from Settings.
  • Hides Settings → Advanced → Server Accounts until the experimental Paykit UI is enabled.
  • Persists and backs up account metadata, serializes lifecycle and reconciliation changes, and supports disabling or restoring LDK tracking without deleting wallet state.
  • Configures additional-wallet scanning and the rolling 1,000-address lookahead.
  • Deterministically derives the receiver Noise key from the wallet's BIP39 seed using domain-separated HKDF-SHA256 with version, network, and receiver-path context; securely caches and verifies it so the wallet seed remains the only backup required.
  • Reuses the derived Noise key across authentication and restarts, and publishes only its receiver marker when live session access becomes available.
  • Updates to Paykit 0.1.0-rc37, LDK Node 0.7.0-rc.57, and Bitkit Core 0.4.2; Bitkit Core now supplies trusted extended-public-key serialization, so Base58Swift and its BigInt dependency are no longer required.

The relay claim contains only the versioned account index, address type, and serialized xpub. Local names and tracking state are not disclosed. Ordinary Pubky Auth remains unchanged.

References:

QA Notes

This slice implements ADR 0001's watch-only-account-v1 claim, which is the currently agreed protocol. ADR 0002's proposed watch-only-account-service-v1 delegation flow has not been agreed and is not part of the current implementation.

Reviewers can exercise the complete Bitkit-side setup and account lifecycle with this disposable test request:

pubkyauth://signin?caps=/pub/paykit/v0/bitkit/server/:rw&relay=https://httprelay.pubky.app/inbox/&secret=e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3s&x-bitkit-claim=watch-only-account-v1

Enable Paykit UI, then scan or paste the URL. It exercises watch-only consent, normal Pubky authorization, account allocation and xpub export, companion-claim delivery followed by AuthToken delivery, local activation, and Server Accounts management. The xpub can be copied from the account details and used for external address derivation, funding, restart discovery, spending, and tracking disable/re-enable testing.

The request secret is public, so this URL must only be used with a disposable regtest wallet. A Paykit Server listener or receiver harness is still required to verify requester-side relay polling, decryption, and signature verification. A staging deployment is intended once Paykit Server runtime/listener composition is available.

Manual Tests

  • Enable Paykit UI → Settings → Advanced: Server Accounts is visible; disable Paykit UI: it is hidden.
  • Scanner/paste → use the disposable setup request above → approve watch-only sharing → complete normal Pubky authorization: the account becomes active.
  • Copy the xpub from Server Accounts → externally derive a nonzero receive address → send funds → restart Bitkit: the additional-wallet full scan discovers the funds.
  • Spend the externally received funds from Bitkit.
  • Settings → Advanced → Server Accounts → disable and re-enable tracking: the same account and balance return without allocating another account.
  • Missing, duplicate, unknown, or mismatched companion parameters/capability are rejected; ordinary Pubky Auth remains unchanged.

Automated Checks

  • WatchOnlyAccountServiceTests.swift
  • PubkyAuthApprovalSheetTests.swift
  • PubkyAuthRequestTests.swift
  • PaykitReceiverNoiseKeyStoreTests.swift
  • Paykit rc37 simulator build succeeded against LDK Node rc.57.
  • The exact E2E app-build mode (DEBUG E2E_BUILD) succeeds with the notification target's watch-only/localization membership dependencies.
  • The iOS and Android receiver-key tests share an independently verified derivation vector and cover persistence, reuse, invalid caches, and wrong-seed fail-closed behavior.
  • Post-delivery normal-auth failure, retry after delivery, restart retry, activation, and wallet Paykit regressions are covered by the selected suites.
  • 101 selected watch-only, Paykit, Pubky Auth, endpoint, and receiver-key tests passed after rebase.
  • SwiftFormat and git diff --check passed.

@greptile-apps

greptile-appsBot commented Jul 15, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds Paykit Server watch-only accounts. The main changes are:

  • Strict parsing for the watch-only companion claim and capability.
  • Companion authorization with tracked setup and retry states.
  • Monotonic account allocation, backup, restore, and reconciliation.
  • LDK scanning and tracking controls for additional wallet accounts.
  • Deterministic receiver Noise-key derivation and persistence.
  • Settings screens for managing account names and tracking.

Confidence Score: 5/5

This looks safe to merge.

  • The updated delivery-error handling keeps the account tracked after the documented post-delivery authorization failure.
  • No blocking issue remains in the reviewed changes.

Important Files Changed

FilenameOverview
Bitkit/Services/PubkyService.swiftAdds companion authorization and preserves account tracking for the documented post-delivery authorization failure.
Bitkit/Services/WatchOnlyAccountService.swiftAdds account allocation, persistence, authorization lifecycle, backup restoration, and tracking reconciliation.
Bitkit/Services/LightningService.swiftIntegrates watch-only accounts with LDK startup, scanning, address lookahead, and tracking controls.

Reviews (3): Last reviewed commit: "fix: derive Paykit Noise key from wallet..." | Re-trigger Greptile

Comment threadBitkit/Services/PubkyService.swift
@ben-kaufman
ben-kaufman marked this pull request as draft July 15, 2026 21:23
@ben-kaufman
ben-kaufman marked this pull request as ready for review July 16, 2026 15:19
@ben-kaufman
ben-kaufman marked this pull request as draft July 20, 2026 11:18
@ben-kaufman
ben-kaufman marked this pull request as ready for review July 20, 2026 11:48
@piotr-iohk

Copy link
Copy Markdown
Collaborator

Same review notes as the companion Android PR: synonymdev/bitkit-android#1084 (comment)

(Please apply the same UI gating / testing / ADR questions there.)

CI / merge

  • Merge conflicts with the base branch (codex/paykit-rc33-receivers) — please rebase/resolve.
  • E2E build-local / build-staging currently fail: LightningService references WatchOnlyAccountRecord from the BitkitNotification target where that type isn’t in scope. Please fix so CI builds again.

@ben-kaufman
ben-kaufmanforce-pushed the codex/paykit-watch-only-accounts branch from 67d11d0 to 3d5c9beCompareJuly 22, 2026 10:15
Base automatically changed from codex/paykit-rc33-receivers to masterJuly 22, 2026 11:10
@ben-kaufman
ben-kaufmanforce-pushed the codex/paykit-watch-only-accounts branch from 990db23 to e4a5c92CompareJuly 22, 2026 11:39
@ben-kaufmanChatGPT Codex Connector

Copy link
Copy Markdown
ContributorAuthor

Thanks — the corresponding iOS items are now addressed on the latest commit.

UI gating

Settings → Advanced → Server Accounts is now only visible when Enable Paykit UI is enabled.

Testing the flow

Reviewers can exercise the Bitkit-side flow using this disposable request:

pubkyauth://signin?caps=/pub/paykit/v0/bitkit/server/:rw&relay=https://httprelay.pubky.app/inbox/&secret=e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3s&x-bitkit-claim=watch-only-account-v1

Enable Paykit UI, then paste or scan it. This exercises the watch-only consent, normal Pubky authorization, account allocation/xpub export, both relay deliveries, local activation, and Server Accounts UI.

The xpub can then be copied from the account details sheet and used to test external address derivation, funding, restart discovery, spending, and disabling/re-enabling tracking. Because the request secret is public, this should only be done with a disposable regtest wallet.

The missing part is requester-side polling, decryption, and signature verification; that requires a Paykit Server listener or receiver harness.

Server staging

The intended next server-side QA step is to deploy Paykit Server on staging once runtime/listener composition exists. There is no staging endpoint yet, and that work is outside these mobile PRs.

Retry and activation behavior

The same post-delivery behavior is covered on iOS:

  • Paykit's documented post-delivery AuthorizationFailure leaves the account authorizing and tracked.
  • Retries and retries after restart reuse the same account index, xpub, and payload.
  • Pre-delivery failures return newly pending accounts to an unloaded state.
  • Activation is protected from caller cancellation.
  • A local activation failure after remote delivery is recovered by retrying the same combined authorization and activating the same account.

Protocol version

ADR 0001's watch-only-account-v1 is the currently agreed protocol and is intentionally implemented here. ADR 0002's proposed watch-only-account-service-v1 delegation flow has not been agreed and is not part of the current implementation.

CI and regression

The branch was rebased and is currently mergeable. The notification-target failure was fixed through the target membership exclusion list; build-local and build-staging now pass. The selected 101 watch-only, Pubky Auth, endpoint, receiver-key, and wallet Paykit regression tests pass.

The existing pubky_paykit E2E fixture issue uses outdated pre-made profiles and reproduces on the base PR, so it is inherited CI noise rather than a regression from this watch-only change.

@piotr-iohk

Copy link
Copy Markdown
Collaborator

Same retest notes as the companion Android PR: synonymdev/bitkit-android#1084 (comment)

iOS-specific bits already covered there: Enter-manually gap for pubkyauth, timeout → Incomplete setup → retry OBS, and the icon/title/copy parity nits.

@ovitrif
ovitrif requested a review from piotr-iohkJuly 23, 2026 20:04
@piotr-iohk

Copy link
Copy Markdown
Collaborator

Retested after the alignment fixes. Looks good to approve on my side.

Verified:

  • Settings row icon / title casing aligned
  • Server Accounts header burger present
  • pubkyauth limited to main scan (Send paste / Enter manually reject as intended)
  • Disposable watch-only setup URL → consent → authorize → Server Accounts still works

No remaining iOS blockers from this pass. Android still has a home-Scan gating bug (home Scan is modeled as Send, so pubkyauth is rejected there); noted on the companion PR.

@jvsena42jvsena42 added this to the 2.5.0 milestone Jul 24, 2026

@jvsena42jvsena42 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Simulator.Screen.Recording.-.iPhone.17.-.2026-07-24.at.10.05.22.mov

Approved with some nits

Comment threadBitkit/Services/PubkyService.swift
Comment threadBitkit/Managers/ScannerManager.swift
@jvsena42
jvsena42 merged commit 95c1766 into masterJul 24, 2026
11 checks passed
@jvsena42
jvsena42 deleted the codex/paykit-watch-only-accounts branch July 24, 2026 13:48
@jvsena42jvsena42 mentioned this pull request Jul 27, 2026
13 tasks
Sign up for freeto 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.

4 participants

@ben-kaufman@piotr-iohk@jvsena42@MrxiaoAxiao
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat: add Paykit server accounts - #630

Merged
jvsena42 merged 19 commits into
masterfrom
codex/paykit-watch-only-accounts
Jul 24, 2026
Merged

feat: add Paykit server accounts#630
jvsena42 merged 19 commits into
masterfrom
codex/paykit-watch-only-accounts

Conversation

@ben-kaufman

@ben-kaufmanben-kaufman commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Description

This PR builds on #620 to add Paykit Server watch-only accounts:

  • Presents a dedicated watch-only consent step before the normal Pubky authorization screen.
  • Requires x-bitkit-claim=watch-only-account-v1 and the exact /pub/paykit/v0/bitkit/server/:rw capability, rejecting missing, unknown, duplicate, or mismatched companion parameters.
  • Uses Paykit companion authorization and fails closed if companion delivery or normal authorization fails.
  • Allocates monotonic BIP84 accounts, reuses the same incomplete account on retry, and exports the exact versioned 84-byte claim payload.
  • Automatically names new accounts from the requesting service; users can rename them later from Settings.
  • Hides Settings → Advanced → Server Accounts until the experimental Paykit UI is enabled.
  • Persists and backs up account metadata, serializes lifecycle and reconciliation changes, and supports disabling or restoring LDK tracking without deleting wallet state.
  • Configures additional-wallet scanning and the rolling 1,000-address lookahead.
  • Deterministically derives the receiver Noise key from the wallet's BIP39 seed using domain-separated HKDF-SHA256 with version, network, and receiver-path context; securely caches and verifies it so the wallet seed remains the only backup required.
  • Reuses the derived Noise key across authentication and restarts, and publishes only its receiver marker when live session access becomes available.
  • Updates to Paykit 0.1.0-rc37, LDK Node 0.7.0-rc.57, and Bitkit Core 0.4.2; Bitkit Core now supplies trusted extended-public-key serialization, so Base58Swift and its BigInt dependency are no longer required.

The relay claim contains only the versioned account index, address type, and serialized xpub. Local names and tracking state are not disclosed. Ordinary Pubky Auth remains unchanged.

References:

QA Notes

This slice implements ADR 0001's watch-only-account-v1 claim, which is the currently agreed protocol. ADR 0002's proposed watch-only-account-service-v1 delegation flow has not been agreed and is not part of the current implementation.

Reviewers can exercise the complete Bitkit-side setup and account lifecycle with this disposable test request:

pubkyauth://signin?caps=/pub/paykit/v0/bitkit/server/:rw&relay=https://httprelay.pubky.app/inbox/&secret=e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3s&x-bitkit-claim=watch-only-account-v1

Enable Paykit UI, then scan or paste the URL. It exercises watch-only consent, normal Pubky authorization, account allocation and xpub export, companion-claim delivery followed by AuthToken delivery, local activation, and Server Accounts management. The xpub can be copied from the account details and used for external address derivation, funding, restart discovery, spending, and tracking disable/re-enable testing.

The request secret is public, so this URL must only be used with a disposable regtest wallet. A Paykit Server listener or receiver harness is still required to verify requester-side relay polling, decryption, and signature verification. A staging deployment is intended once Paykit Server runtime/listener composition is available.

Manual Tests

  • Enable Paykit UI → Settings → Advanced: Server Accounts is visible; disable Paykit UI: it is hidden.
  • Scanner/paste → use the disposable setup request above → approve watch-only sharing → complete normal Pubky authorization: the account becomes active.
  • Copy the xpub from Server Accounts → externally derive a nonzero receive address → send funds → restart Bitkit: the additional-wallet full scan discovers the funds.
  • Spend the externally received funds from Bitkit.
  • Settings → Advanced → Server Accounts → disable and re-enable tracking: the same account and balance return without allocating another account.
  • Missing, duplicate, unknown, or mismatched companion parameters/capability are rejected; ordinary Pubky Auth remains unchanged.

Automated Checks

  • WatchOnlyAccountServiceTests.swift
  • PubkyAuthApprovalSheetTests.swift
  • PubkyAuthRequestTests.swift
  • PaykitReceiverNoiseKeyStoreTests.swift
  • Paykit rc37 simulator build succeeded against LDK Node rc.57.
  • The exact E2E app-build mode (DEBUG E2E_BUILD) succeeds with the notification target's watch-only/localization membership dependencies.
  • The iOS and Android receiver-key tests share an independently verified derivation vector and cover persistence, reuse, invalid caches, and wrong-seed fail-closed behavior.
  • Post-delivery normal-auth failure, retry after delivery, restart retry, activation, and wallet Paykit regressions are covered by the selected suites.
  • 101 selected watch-only, Paykit, Pubky Auth, endpoint, and receiver-key tests passed after rebase.
  • SwiftFormat and git diff --check passed.

@greptile-apps

greptile-appsBot commented Jul 15, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds Paykit Server watch-only accounts. The main changes are:

  • Strict parsing for the watch-only companion claim and capability.
  • Companion authorization with tracked setup and retry states.
  • Monotonic account allocation, backup, restore, and reconciliation.
  • LDK scanning and tracking controls for additional wallet accounts.
  • Deterministic receiver Noise-key derivation and persistence.
  • Settings screens for managing account names and tracking.

Confidence Score: 5/5

This looks safe to merge.

  • The updated delivery-error handling keeps the account tracked after the documented post-delivery authorization failure.
  • No blocking issue remains in the reviewed changes.

Important Files Changed

FilenameOverview
Bitkit/Services/PubkyService.swiftAdds companion authorization and preserves account tracking for the documented post-delivery authorization failure.
Bitkit/Services/WatchOnlyAccountService.swiftAdds account allocation, persistence, authorization lifecycle, backup restoration, and tracking reconciliation.
Bitkit/Services/LightningService.swiftIntegrates watch-only accounts with LDK startup, scanning, address lookahead, and tracking controls.

Reviews (3): Last reviewed commit: "fix: derive Paykit Noise key from wallet..." | Re-trigger Greptile

Comment threadBitkit/Services/PubkyService.swift
@ben-kaufman
ben-kaufman marked this pull request as draft July 15, 2026 21:23
@ben-kaufman
ben-kaufman marked this pull request as ready for review July 16, 2026 15:19
@ben-kaufman
ben-kaufman marked this pull request as draft July 20, 2026 11:18
@ben-kaufman
ben-kaufman marked this pull request as ready for review July 20, 2026 11:48
@piotr-iohk

Copy link
Copy Markdown
Collaborator

Same review notes as the companion Android PR: synonymdev/bitkit-android#1084 (comment)

(Please apply the same UI gating / testing / ADR questions there.)

CI / merge

  • Merge conflicts with the base branch (codex/paykit-rc33-receivers) — please rebase/resolve.
  • E2E build-local / build-staging currently fail: LightningService references WatchOnlyAccountRecord from the BitkitNotification target where that type isn’t in scope. Please fix so CI builds again.

@ben-kaufman
ben-kaufmanforce-pushed the codex/paykit-watch-only-accounts branch from 67d11d0 to 3d5c9beCompareJuly 22, 2026 10:15
Base automatically changed from codex/paykit-rc33-receivers to masterJuly 22, 2026 11:10
@ben-kaufman
ben-kaufmanforce-pushed the codex/paykit-watch-only-accounts branch from 990db23 to e4a5c92CompareJuly 22, 2026 11:39
@ben-kaufmanChatGPT Codex Connector

Copy link
Copy Markdown
ContributorAuthor

Thanks — the corresponding iOS items are now addressed on the latest commit.

UI gating

Settings → Advanced → Server Accounts is now only visible when Enable Paykit UI is enabled.

Testing the flow

Reviewers can exercise the Bitkit-side flow using this disposable request:

pubkyauth://signin?caps=/pub/paykit/v0/bitkit/server/:rw&relay=https://httprelay.pubky.app/inbox/&secret=e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3s&x-bitkit-claim=watch-only-account-v1

Enable Paykit UI, then paste or scan it. This exercises the watch-only consent, normal Pubky authorization, account allocation/xpub export, both relay deliveries, local activation, and Server Accounts UI.

The xpub can then be copied from the account details sheet and used to test external address derivation, funding, restart discovery, spending, and disabling/re-enabling tracking. Because the request secret is public, this should only be done with a disposable regtest wallet.

The missing part is requester-side polling, decryption, and signature verification; that requires a Paykit Server listener or receiver harness.

Server staging

The intended next server-side QA step is to deploy Paykit Server on staging once runtime/listener composition exists. There is no staging endpoint yet, and that work is outside these mobile PRs.

Retry and activation behavior

The same post-delivery behavior is covered on iOS:

  • Paykit's documented post-delivery AuthorizationFailure leaves the account authorizing and tracked.
  • Retries and retries after restart reuse the same account index, xpub, and payload.
  • Pre-delivery failures return newly pending accounts to an unloaded state.
  • Activation is protected from caller cancellation.
  • A local activation failure after remote delivery is recovered by retrying the same combined authorization and activating the same account.

Protocol version

ADR 0001's watch-only-account-v1 is the currently agreed protocol and is intentionally implemented here. ADR 0002's proposed watch-only-account-service-v1 delegation flow has not been agreed and is not part of the current implementation.

CI and regression

The branch was rebased and is currently mergeable. The notification-target failure was fixed through the target membership exclusion list; build-local and build-staging now pass. The selected 101 watch-only, Pubky Auth, endpoint, receiver-key, and wallet Paykit regression tests pass.

The existing pubky_paykit E2E fixture issue uses outdated pre-made profiles and reproduces on the base PR, so it is inherited CI noise rather than a regression from this watch-only change.

@piotr-iohk

Copy link
Copy Markdown
Collaborator

Same retest notes as the companion Android PR: synonymdev/bitkit-android#1084 (comment)

iOS-specific bits already covered there: Enter-manually gap for pubkyauth, timeout → Incomplete setup → retry OBS, and the icon/title/copy parity nits.

@ovitrif
ovitrif requested a review from piotr-iohkJuly 23, 2026 20:04
@piotr-iohk

Copy link
Copy Markdown
Collaborator

Retested after the alignment fixes. Looks good to approve on my side.

Verified:

  • Settings row icon / title casing aligned
  • Server Accounts header burger present
  • pubkyauth limited to main scan (Send paste / Enter manually reject as intended)
  • Disposable watch-only setup URL → consent → authorize → Server Accounts still works

No remaining iOS blockers from this pass. Android still has a home-Scan gating bug (home Scan is modeled as Send, so pubkyauth is rejected there); noted on the companion PR.

@jvsena42jvsena42 added this to the 2.5.0 milestone Jul 24, 2026

@jvsena42jvsena42 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Simulator.Screen.Recording.-.iPhone.17.-.2026-07-24.at.10.05.22.mov

Approved with some nits

Comment threadBitkit/Services/PubkyService.swift
Comment threadBitkit/Managers/ScannerManager.swift
@jvsena42
jvsena42 merged commit 95c1766 into masterJul 24, 2026
11 checks passed
@jvsena42
jvsena42 deleted the codex/paykit-watch-only-accounts branch July 24, 2026 13:48
@jvsena42jvsena42 mentioned this pull request Jul 27, 2026
13 tasks
Sign up for freeto 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.

4 participants

@ben-kaufman@piotr-iohk@jvsena42@MrxiaoAxiao
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

feat: add Paykit server accounts - #630

Merged
jvsena42 merged 19 commits into
masterfrom
codex/paykit-watch-only-accounts
Jul 24, 2026
Merged

feat: add Paykit server accounts#630
jvsena42 merged 19 commits into
masterfrom
codex/paykit-watch-only-accounts

Conversation

@ben-kaufman

@ben-kaufmanben-kaufman commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Description

This PR builds on #620 to add Paykit Server watch-only accounts:

  • Presents a dedicated watch-only consent step before the normal Pubky authorization screen.
  • Requires x-bitkit-claim=watch-only-account-v1 and the exact /pub/paykit/v0/bitkit/server/:rw capability, rejecting missing, unknown, duplicate, or mismatched companion parameters.
  • Uses Paykit companion authorization and fails closed if companion delivery or normal authorization fails.
  • Allocates monotonic BIP84 accounts, reuses the same incomplete account on retry, and exports the exact versioned 84-byte claim payload.
  • Automatically names new accounts from the requesting service; users can rename them later from Settings.
  • Hides Settings → Advanced → Server Accounts until the experimental Paykit UI is enabled.
  • Persists and backs up account metadata, serializes lifecycle and reconciliation changes, and supports disabling or restoring LDK tracking without deleting wallet state.
  • Configures additional-wallet scanning and the rolling 1,000-address lookahead.
  • Deterministically derives the receiver Noise key from the wallet's BIP39 seed using domain-separated HKDF-SHA256 with version, network, and receiver-path context; securely caches and verifies it so the wallet seed remains the only backup required.
  • Reuses the derived Noise key across authentication and restarts, and publishes only its receiver marker when live session access becomes available.
  • Updates to Paykit 0.1.0-rc37, LDK Node 0.7.0-rc.57, and Bitkit Core 0.4.2; Bitkit Core now supplies trusted extended-public-key serialization, so Base58Swift and its BigInt dependency are no longer required.

The relay claim contains only the versioned account index, address type, and serialized xpub. Local names and tracking state are not disclosed. Ordinary Pubky Auth remains unchanged.

References:

QA Notes

This slice implements ADR 0001's watch-only-account-v1 claim, which is the currently agreed protocol. ADR 0002's proposed watch-only-account-service-v1 delegation flow has not been agreed and is not part of the current implementation.

Reviewers can exercise the complete Bitkit-side setup and account lifecycle with this disposable test request:

pubkyauth://signin?caps=/pub/paykit/v0/bitkit/server/:rw&relay=https://httprelay.pubky.app/inbox/&secret=e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3s&x-bitkit-claim=watch-only-account-v1

Enable Paykit UI, then scan or paste the URL. It exercises watch-only consent, normal Pubky authorization, account allocation and xpub export, companion-claim delivery followed by AuthToken delivery, local activation, and Server Accounts management. The xpub can be copied from the account details and used for external address derivation, funding, restart discovery, spending, and tracking disable/re-enable testing.

The request secret is public, so this URL must only be used with a disposable regtest wallet. A Paykit Server listener or receiver harness is still required to verify requester-side relay polling, decryption, and signature verification. A staging deployment is intended once Paykit Server runtime/listener composition is available.

Manual Tests

  • Enable Paykit UI → Settings → Advanced: Server Accounts is visible; disable Paykit UI: it is hidden.
  • Scanner/paste → use the disposable setup request above → approve watch-only sharing → complete normal Pubky authorization: the account becomes active.
  • Copy the xpub from Server Accounts → externally derive a nonzero receive address → send funds → restart Bitkit: the additional-wallet full scan discovers the funds.
  • Spend the externally received funds from Bitkit.
  • Settings → Advanced → Server Accounts → disable and re-enable tracking: the same account and balance return without allocating another account.
  • Missing, duplicate, unknown, or mismatched companion parameters/capability are rejected; ordinary Pubky Auth remains unchanged.

Automated Checks

  • WatchOnlyAccountServiceTests.swift
  • PubkyAuthApprovalSheetTests.swift
  • PubkyAuthRequestTests.swift
  • PaykitReceiverNoiseKeyStoreTests.swift
  • Paykit rc37 simulator build succeeded against LDK Node rc.57.
  • The exact E2E app-build mode (DEBUG E2E_BUILD) succeeds with the notification target's watch-only/localization membership dependencies.
  • The iOS and Android receiver-key tests share an independently verified derivation vector and cover persistence, reuse, invalid caches, and wrong-seed fail-closed behavior.
  • Post-delivery normal-auth failure, retry after delivery, restart retry, activation, and wallet Paykit regressions are covered by the selected suites.
  • 101 selected watch-only, Paykit, Pubky Auth, endpoint, and receiver-key tests passed after rebase.
  • SwiftFormat and git diff --check passed.

@greptile-apps

greptile-appsBot commented Jul 15, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds Paykit Server watch-only accounts. The main changes are:

  • Strict parsing for the watch-only companion claim and capability.
  • Companion authorization with tracked setup and retry states.
  • Monotonic account allocation, backup, restore, and reconciliation.
  • LDK scanning and tracking controls for additional wallet accounts.
  • Deterministic receiver Noise-key derivation and persistence.
  • Settings screens for managing account names and tracking.

Confidence Score: 5/5

This looks safe to merge.

  • The updated delivery-error handling keeps the account tracked after the documented post-delivery authorization failure.
  • No blocking issue remains in the reviewed changes.

Important Files Changed

FilenameOverview
Bitkit/Services/PubkyService.swiftAdds companion authorization and preserves account tracking for the documented post-delivery authorization failure.
Bitkit/Services/WatchOnlyAccountService.swiftAdds account allocation, persistence, authorization lifecycle, backup restoration, and tracking reconciliation.
Bitkit/Services/LightningService.swiftIntegrates watch-only accounts with LDK startup, scanning, address lookahead, and tracking controls.

Reviews (3): Last reviewed commit: "fix: derive Paykit Noise key from wallet..." | Re-trigger Greptile

Comment threadBitkit/Services/PubkyService.swift
@ben-kaufman
ben-kaufman marked this pull request as draft July 15, 2026 21:23
@ben-kaufman
ben-kaufman marked this pull request as ready for review July 16, 2026 15:19
@ben-kaufman
ben-kaufman marked this pull request as draft July 20, 2026 11:18
@ben-kaufman
ben-kaufman marked this pull request as ready for review July 20, 2026 11:48
@piotr-iohk

Copy link
Copy Markdown
Collaborator

Same review notes as the companion Android PR: synonymdev/bitkit-android#1084 (comment)

(Please apply the same UI gating / testing / ADR questions there.)

CI / merge

  • Merge conflicts with the base branch (codex/paykit-rc33-receivers) — please rebase/resolve.
  • E2E build-local / build-staging currently fail: LightningService references WatchOnlyAccountRecord from the BitkitNotification target where that type isn’t in scope. Please fix so CI builds again.

@ben-kaufman
ben-kaufmanforce-pushed the codex/paykit-watch-only-accounts branch from 67d11d0 to 3d5c9beCompareJuly 22, 2026 10:15
Base automatically changed from codex/paykit-rc33-receivers to masterJuly 22, 2026 11:10
@ben-kaufman
ben-kaufmanforce-pushed the codex/paykit-watch-only-accounts branch from 990db23 to e4a5c92CompareJuly 22, 2026 11:39
@ben-kaufmanChatGPT Codex Connector

Copy link
Copy Markdown
ContributorAuthor

Thanks — the corresponding iOS items are now addressed on the latest commit.

UI gating

Settings → Advanced → Server Accounts is now only visible when Enable Paykit UI is enabled.

Testing the flow

Reviewers can exercise the Bitkit-side flow using this disposable request:

pubkyauth://signin?caps=/pub/paykit/v0/bitkit/server/:rw&relay=https://httprelay.pubky.app/inbox/&secret=e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3s&x-bitkit-claim=watch-only-account-v1

Enable Paykit UI, then paste or scan it. This exercises the watch-only consent, normal Pubky authorization, account allocation/xpub export, both relay deliveries, local activation, and Server Accounts UI.

The xpub can then be copied from the account details sheet and used to test external address derivation, funding, restart discovery, spending, and disabling/re-enabling tracking. Because the request secret is public, this should only be done with a disposable regtest wallet.

The missing part is requester-side polling, decryption, and signature verification; that requires a Paykit Server listener or receiver harness.

Server staging

The intended next server-side QA step is to deploy Paykit Server on staging once runtime/listener composition exists. There is no staging endpoint yet, and that work is outside these mobile PRs.

Retry and activation behavior

The same post-delivery behavior is covered on iOS:

  • Paykit's documented post-delivery AuthorizationFailure leaves the account authorizing and tracked.
  • Retries and retries after restart reuse the same account index, xpub, and payload.
  • Pre-delivery failures return newly pending accounts to an unloaded state.
  • Activation is protected from caller cancellation.
  • A local activation failure after remote delivery is recovered by retrying the same combined authorization and activating the same account.

Protocol version

ADR 0001's watch-only-account-v1 is the currently agreed protocol and is intentionally implemented here. ADR 0002's proposed watch-only-account-service-v1 delegation flow has not been agreed and is not part of the current implementation.

CI and regression

The branch was rebased and is currently mergeable. The notification-target failure was fixed through the target membership exclusion list; build-local and build-staging now pass. The selected 101 watch-only, Pubky Auth, endpoint, receiver-key, and wallet Paykit regression tests pass.

The existing pubky_paykit E2E fixture issue uses outdated pre-made profiles and reproduces on the base PR, so it is inherited CI noise rather than a regression from this watch-only change.

@piotr-iohk

Copy link
Copy Markdown
Collaborator

Same retest notes as the companion Android PR: synonymdev/bitkit-android#1084 (comment)

iOS-specific bits already covered there: Enter-manually gap for pubkyauth, timeout → Incomplete setup → retry OBS, and the icon/title/copy parity nits.

@ovitrif
ovitrif requested a review from piotr-iohkJuly 23, 2026 20:04
@piotr-iohk

Copy link
Copy Markdown
Collaborator

Retested after the alignment fixes. Looks good to approve on my side.

Verified:

  • Settings row icon / title casing aligned
  • Server Accounts header burger present
  • pubkyauth limited to main scan (Send paste / Enter manually reject as intended)
  • Disposable watch-only setup URL → consent → authorize → Server Accounts still works

No remaining iOS blockers from this pass. Android still has a home-Scan gating bug (home Scan is modeled as Send, so pubkyauth is rejected there); noted on the companion PR.

@jvsena42jvsena42 added this to the 2.5.0 milestone Jul 24, 2026

@jvsena42jvsena42 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Simulator.Screen.Recording.-.iPhone.17.-.2026-07-24.at.10.05.22.mov

Approved with some nits

Comment threadBitkit/Services/PubkyService.swift
Comment threadBitkit/Managers/ScannerManager.swift
@jvsena42
jvsena42 merged commit 95c1766 into masterJul 24, 2026
11 checks passed
@jvsena42
jvsena42 deleted the codex/paykit-watch-only-accounts branch July 24, 2026 13:48
@jvsena42jvsena42 mentioned this pull request Jul 27, 2026
13 tasks
Sign up for freeto 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.

4 participants

@ben-kaufman@piotr-iohk@jvsena42@MrxiaoAxiao