Skip to content

feat: upgrade paykit to rc51 - #1200

Open
ben-kaufman wants to merge 13 commits into
masterfrom
codex/paykit-rc50-auth
Open

feat: upgrade paykit to rc51#1200
ben-kaufman wants to merge 13 commits into
masterfrom
codex/paykit-rc50-auth

Conversation

@ben-kaufman

@ben-kaufman ben-kaufman commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Fixes #1219

This PR:

  1. Upgrades Paykit from 0.1.0-rc46 to 0.1.0-rc51.
  2. Adopts app-scoped Pubky grants using Bitkit's stable client ID.
  3. Revokes Bitkit's grant on normal sign-out while preserving local state when remote revocation fails.
  4. Restricts local-only session forgetting to destructive reset and backup-replacement flows.
  5. Fixes payment requests disappearing from history after requests are exchanged and paid in both directions.

Description

Bitkit now identifies itself as bitkit.to on mainnet and staging.bitkit.to elsewhere. Normal sign-out remotely revokes only Bitkit's current grant. If revocation cannot be confirmed, the profile and private Paykit state remain available so the user can retry.

Completed Ring authentication that is later canceled, and identity creation that fails after activating a session, also attempt secure revocation. Explicit wallet wipe and backup replacement forget the local session. Existing local E2E homeserver behavior remains unchanged.

Paykit rc51 also fixes replay ordering when incoming and outgoing Payment Request lifecycles interleave. Each request's events are derived in the correct order, preserving its payer/payee role so the existing history mapping retains the row. The fix is in paykit-rs #151 and is available in the published release before that PR merges.

Preview

N/A — no visual changes.

QA Notes

Manual Tests

  • 1. Pubky profile → Sign Out while online: Bitkit signs out and returns to the disconnected profile state.
  • 2. Pubky profile → interrupt network access → Sign Out: Bitkit shows an error and keeps the profile and private Paykit state; restore network access and retry successfully.
  • 3. Pubky auth → approve another app → Sign Out of Bitkit: Bitkit's session is revoked while the other app remains authorized.
  • 4. regression: restore a wallet backup with different Pubky state: the previous local session is forgotten and the backup identity is installed.
  • 5. regression: two privately linked wallets → each creates a 1,000-sat Lightning request and a 25,000-sat on-chain request → the peer pays all four → Payment Requests: both wallets retain all four rows after refresh and app restart.

Automated Checks

  • PubkyRepoTest.kt: covers canceled completed authentication revocation, revocation failure, wallet wipe, and backup replacement.
  • ProfileViewModelTest.kt and EditProfileViewModelTest.kt: cover preserving private Paykit state when sign-out fails.
  • PaykitSdkServiceTest.kt: covers the stable Bitkit client ID and Pubky client configuration.
  • Upstream rc51 regression covers interleaved bidirectional request events; Paykit tests and both binding builds passed.
  • Local rc51 just lint passed; no app tests were added or removed.
  • Local rc51 compilation and unit-test execution were blocked: configured GitHub Packages credentials return HTTP 401, and the offline cache lacks the unchanged bitkit-core-android:0.5.14 and ldk-node-android:0.7.0-rc.66 dependencies. CI must verify the app against the published package.

Linked Issues/Tasks

@greptile-apps

greptile-apps Bot commented Aug 31, 2026

Copy link
Copy Markdown

Greptile Summary

Upgrades Paykit to rc50 and adopts app-scoped Pubky grants, remote grant revocation on normal sign-out, and local-only session forgetting for destructive flows.

  • Configures Paykit with Bitkit’s stable environment-specific client ID.
  • Preserves profile and private Paykit state when remote revocation fails.
  • Adds cleanup for cancelled authentication and incomplete identity creation.
  • Updates session lifecycle tests and release notes.

Confidence Score: 2/5

This PR should not merge until cancellation reliably revokes newly activated grants and failed sign-out no longer leaves authenticated users with removed payment endpoints.

Session activation can complete before its cleanup guard is set, leaving a valid grant after cancellation, while the sign-out ordering removes payment publication before a revocation failure returns with the identity still active.

Files Needing Attention: app/src/main/java/to/bitkit/repositories/PubkyRepo.kt

Security Review

Identity-creation cancellation can leave an app-scoped grant and persisted session active because the cleanup guard is assigned only after the session-activating service call returns.

Important Files Changed

Filename Overview
app/src/main/java/to/bitkit/repositories/PubkyRepo.kt Adds revocation-aware authentication, creation, sign-out, wipe, and restore behavior, but cancellation can bypass revocation and failed sign-out can leave endpoint publication inconsistent.
app/src/main/java/to/bitkit/services/PaykitSdkService.kt Adopts rc50 client IDs and local session forgetting while preserving the existing serialized SDK boundary.
app/src/main/java/to/bitkit/services/PubkyService.kt Replaces force/local-clear APIs with the rc50 local forget operation and retains queued service execution.
app/src/main/java/to/bitkit/ui/screens/profile/ProfileViewModel.kt Clears private Paykit state only after confirmed remote sign-out and preserves it on failure.
app/src/main/java/to/bitkit/ui/screens/profile/EditProfileViewModel.kt Aligns profile disconnect behavior with fail-closed remote grant revocation.

Sequence Diagram

sequenceDiagram
    participant UI
    participant Repo as PubkyRepo
    participant Paykit
    participant Pubky
    UI->>Repo: Sign out
    Repo->>Paykit: Remove published endpoints
    Paykit-->>Repo: Endpoints removed
    Repo->>Pubky: Revoke Bitkit grant
    alt Revocation succeeds
        Pubky-->>Repo: Success
        Repo->>Repo: Clear local identity and Paykit state
        Repo-->>UI: Signed out
    else Revocation fails
        Pubky-->>Repo: Error
        Repo-->>UI: Preserve identity and report error
        Note over Repo,Paykit: Published endpoints remain removed
    end
Loading

Reviews (1): Last reviewed commit: "feat: upgrade paykit auth to rc50" | Re-trigger Greptile

Comment thread app/src/main/java/to/bitkit/repositories/PubkyRepo.kt Outdated
Comment thread app/src/main/java/to/bitkit/repositories/PubkyRepo.kt Outdated
ovitrif

This comment was marked as resolved.

@ben-kaufman

Copy link
Copy Markdown
Contributor Author

The cancellation and cleanup findings are fixed in signed commit dd3b7bad. Successful completeAuth() and its completion marker now cross a minimal NonCancellable boundary, so cancellation cannot leave a persisted grant while skipping cleanup; ordinary completion failures still preserve existing sessions. The focused tests cover cancellation after SDK completion and post-activation createIdentity() failure cleanup. All CI, lint, detekt, build, and E2E checks are green on the current head. @ovitrif please re-review.

ovitrif
ovitrif previously approved these changes Sep 1, 2026

@ovitrif ovitrif left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

utACK

piotr-iohk
piotr-iohk previously approved these changes Sep 2, 2026

@piotr-iohk piotr-iohk left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

e2e ACK.

Latest (dd3b7ba) with matching e2e branch codex/paykit-rc50-auth (#212). Recreated the two staging Paykit fixture pubkys. e2e-tests-staging - pubky_paykit green. Full CI green.

Manual on Pixel emu: online Delete/Disconnect clears session + secret for pubkyif…ejhsa1y. Offline Delete: transport_error, profile kept. Offline Disconnect from that dialog: endpoint cleanup WARN, then ~22s later network returned and session cleared. Offline Disconnect waited for network instead of a clean fail-closed error. Not a blocker.

Did not retest other-app grant stays authorized, or backup replace.

@jvsena42

jvsena42 commented Sep 2, 2026

Copy link
Copy Markdown
Member

Non blocking: I got this error when trying to sign in with Pubky Ring

{
  "action": "unknown",
  "rawInput": "pubkyauth://signin_grant?caps=%2Fpub%2Fpaykit%2Fv0%2Fbitkit%2Fwallet%2F%3Arw%2C%2Fpub%2Fpaykit%2Fv0%2Fprivate%2Fbitkit%2Fwallet%2F%3Arw%2C%2Fpub%2Fstaging.bitkit.to%2Fbitkit%2Fwallet%2F%3Arw&relay=https%3A%2F%2Fhttprelay.pubky.app%2Finbox&secret=9P-JtlM1i5HvaJnmru5fZhgYU5dvpK3sL-KNi-1eAng&cid=staging.bitkit.to&cpk=cc4amfpzrugsxkbqgia9he9yy9jezq4kcjo3wdsgoozar4zt1b5o&x-success=bitkit%3A%2F%2Fpubky-auth%2Fsuccess%3Fnonce%3D90e53840-9b30-4e4d-9751-b6b018beca25&x-cancel=bitkit%3A%2F%2Fpubky-auth%2Fcancel%3Fnonce%3D90e53840-9b30-4e4d-9751-b6b018beca25&x-error=bitkit%3A%2F%2Fpubky-auth%2Ferror%3Fnonce%3D90e53840-9b30-4e4d-9751-b6b018beca25&x-source=Bitkit",
  "error": "Unrecognized format. Expected a recovery phrase, invite code, auth URL, or session request."
}

Probably same issue of jvsena42/loopky#130

Screen_recording_20260902_142153.mp4

Will probably work on next Ring release

@jvsena42 jvsena42 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.

Review notes from testing this branch on a dev build (regtest, Pixel_9 emulator).

The first two comments were reproduced on device with logs attached inline; the rest come from reading the diff. The happy paths all worked: create identity, restore across restart, and online profile delete with full keychain teardown.

Comment thread gradle/libs.versions.toml Outdated
Comment thread app/src/main/java/to/bitkit/repositories/PubkyRepo.kt
Comment thread app/src/main/java/to/bitkit/repositories/PubkyRepo.kt Outdated
Comment thread app/src/main/java/to/bitkit/repositories/PubkyRepo.kt
Comment thread app/src/main/java/to/bitkit/services/PaykitSdkService.kt
@ben-kaufman
ben-kaufman dismissed stale reviews from ovitrif and piotr-iohk via 6549130 September 2, 2026 18:04
@ben-kaufman

Copy link
Copy Markdown
Contributor Author

@jvsena42 Confirmed the remaining Ring observation separately: rc50 intentionally emits the grant-auth request shape, while the currently installed Ring build does not yet recognize it. That is a Ring compatibility gap for its next release, not another Bitkit change. We will retest the end-to-end handoff with a Ring build that supports grant auth.

ovitrif
ovitrif previously approved these changes Sep 2, 2026

@ovitrif ovitrif left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

utACK

jvsena42

This comment was marked as outdated.

@jvsena42 jvsena42 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.

Follow-up on error localization in 65491308d/54a620df0.

Comment thread app/src/main/java/to/bitkit/ui/screens/profile/PubkyAuthApprovalViewModel.kt Outdated

@jvsena42 jvsena42 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.

Follow-up on the sign-out cancellation hardening in 54a620df0.

Comment thread app/src/main/java/to/bitkit/repositories/PubkyRepo.kt

@jvsena42 jvsena42 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.

One more on the client-ID change.

Comment thread app/src/main/java/to/bitkit/services/PaykitSdkService.kt

@jvsena42 jvsena42 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.

The consequence of the client-ID change on the companion-claim path.

Comment thread app/src/main/java/to/bitkit/services/PaykitSdkService.kt
jvsena42
jvsena42 previously approved these changes Sep 3, 2026

@jvsena42 jvsena42 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.

Approved with one non-blocking comment

ovitrif

This comment was marked as resolved.

piotr-iohk
piotr-iohk previously approved these changes Sep 4, 2026

@piotr-iohk piotr-iohk left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

lgtm

jvsena42
jvsena42 previously approved these changes Sep 4, 2026
@ben-kaufman
ben-kaufman dismissed stale reviews from jvsena42 and piotr-iohk via a681dfc September 4, 2026 12:24
@ben-kaufman
ben-kaufman force-pushed the codex/paykit-rc50-auth branch from 21d5a06 to a681dfc Compare September 4, 2026 12:24
@ben-kaufman

Copy link
Copy Markdown
Contributor Author

Follow-up cross-platform lifecycle audit is fixed in a681dfc6c: auth cleanup is armed before activation can persist a session, profile deletion clears publication intent before a failed sign-out can queue republishing, backup restore proceeds safely when old-session revocation fails, and keychain teardown now attempts both credentials in session-first order. Regression tests cover each edge case. The branch was also rebased onto current master 4bb9d929f; detekt succeeds with only pre-existing repository warnings.

@ben-kaufman
ben-kaufman requested a review from jvsena42 September 4, 2026 12:27
@ben-kaufman

Copy link
Copy Markdown
Contributor Author

One final self-review follow-up is in d2634de17: the explicit forget-session path now resets the in-memory Paykit runtime in finally, even when SDK/keychain teardown throws, so restore cannot continue with the old runtime instance. Detekt remains green apart from the existing baseline warnings.

@ben-kaufman ben-kaufman changed the title feat: upgrade paykit auth to rc50 feat: upgrade paykit to rc51 Sep 4, 2026

@ovitrif ovitrif left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The ghost-session fix is close but stops one level short. discardAbandonedSession() now falls back to forgetSessionAccess() when the remote revoke fails, and that is the right shape, but forgetSessionAccess() does not delete the keychain credentials the way the code it replaced did. PubkyRepo already has a network-independent clearLocalState() that both signOut() and wipeLocalState() use; the abandoned-session path is the only cleanup that never reaches it, so a canceled Ring completion or a failed identity creation can still leave a live grant behind PubkyAuthState.Idle when both calls fail.

One non-blocking note follows: the new fallback has no test that would fail if it stopped clearing the session.

runSuspendCatching {
withContext(NonCancellable + ioDispatcher) {
pubkyService.clearSessionAccess()
pubkyService.forgetSessionAccess()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This fallback calls pubkyService.forgetSessionAccess() when the remote revoke fails, but its result only reaches .onFailure { Logger.warn(...) }, so a second failure is swallowed and nothing else clears the session. forgetSessionAccess() in PaykitSdkService.kt:802 only calls handle.forgetSessionAccess() and resetRuntime(); it never touches Keychain.Key.PAYKIT_SESSION. The base's clearSessionAccessLocked() did delete both keys directly on this path, and this PR removed that. What makes the gap concrete is that PubkyRepo still has a network-independent local teardown: clearLocalState() at :1299 deletes PAYKIT_SESSION and PUBKY_SECRET_KEY under runCatching, and signOut() calls it at :1118 while wipeLocalState() calls it at :1128. discardAbandonedSession() is the only cleanup path that never does, so it depends entirely on the SDK calling back into PaykitSdkSessionProvider.clearSessionAccess() (:1100), which has no in-app caller to guarantee it. When both calls fail, completeAuthentication() still reaches restoreAuthStateAfterAuthFlow() and shows PubkyAuthState.Idle while the grant survives in the keychain, and initialize() reads that key on the next launch and restores it as InitResult.Restored. That is the same ghost session this fallback was added to close, one level deeper. Could we call the existing clearLocalState() as a last resort when the forgetSessionAccess() fallback also fails, so the keychain always matches PubkyAuthState.Idle?

assertTrue(result.isFailure)
verifyBlocking(pubkyService) { clearSessionAccess() }
verifyBlocking(pubkyService) { signOut() }
verifyBlocking(pubkyService) { forgetSessionAccess() }

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This test stubs only pubkyService.signOut() to throw and then asserts that forgetSessionAccess() was called, so it proves the fallback is invoked but not that the abandoned session is actually gone. Nothing here stubs forgetSessionAccess() to fail as well, and nothing asserts keychain.delete(Keychain.Key.PAYKIT_SESSION.name) — the sibling cases at :828-830 and :1089-1091 do make that assertion, so the style is already established in this file. That matters because the whole point of this change is that PubkyAuthState.Idle can no longer hide a persisted session, and as written both added tests would still pass if forgetSessionAccess() left PAYKIT_SESSION behind. Could we add a case where pubkyService.forgetSessionAccess() also throws and assert the session credentials are cleared anyway?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: payment request lists diverge after cross-device pay

4 participants