Skip to content

Self-hosted: have the signer report which wallet actually signed - #1366

Merged
feruzm merged 2 commits into
developfrom
feature/self-hosted-report-signing-wallet
Aug 5, 2026
Merged

Self-hosted: have the signer report which wallet actually signed#1366
feruzm merged 2 commits into
developfrom
feature/self-hosted-report-signing-wallet

Conversation

@feruzm

@feruzm feruzm commented Aug 5, 2026

Copy link
Copy Markdown
Member

Closes the residual case in #1361, the one #1360 and #1362 could not reach because it is not a hardcoded string.

signBufferWithExtension takes a preferred wallet id, and when that extension has been uninstalled mid-session it deliberately does not clear the stored preference and falls through to Keeper-first detection:

if (instance) return signViaKeychainLike(instance, ...);
// Chosen extension is gone (uninstalled); forget the stale preference.
if (!preferredId) setPreferredExtensionId(account, null);

So Keychain is recorded, Keychain is uninstalled, Keeper prompts, the user cancels, and the message named Keychain — the right name for the recorded preference, the wrong name for the wallet that actually appeared.

The fix

The additive option the issue named, not the behaviour-changing one. Nothing about which wallet gets asked to sign changes; only what the caller is told afterwards.

  • signBufferWithExtension returns SignedByExtension, the signing result plus the id it actually used.
  • getHostingToken names signed.extension instead of the session's stored user.extension.
  • Login records the wallet that signed rather than the one it asked for, so a preference the browser cannot honour is never written. That is where the mismatch begins.

Resolution reports its own id rather than mapping the resolved instance back to one. Keeper aliases itself onto window.hive_keychain, so comparing object identity against getKeychainInstance() would label Keeper "Keychain" on a Keeper-only browser, reintroducing the exact confusion this work removes.

Tests

Driven against a stubbed window and a real localStorage. The preference store's own try/catch would swallow a missing localStorage and return null, which happens to be the value these cases want, so the tests would have passed for a reason unrelated to what they assert.

Cases: the asked-for wallet when present; the mid-session uninstall naming Keeper; Keeper's alias not being reported as Keychain; rejection when nothing is installed; and the cancellation message agreeing with the signer.

The call site is asserted separately, and here is why

Every behaviour test above passed with getHostingToken reverted to user.extension. The pieces being correct is not the property under test; the caller using them is. getHostingToken cannot be driven here because it opens a real socket to the hosting API, so the argument it passes is asserted through the AST.

Mutations run, each failing only its own case: reverting the call site to user.extension; reporting extId ?? resolved.id in the fallback, which is the bug restated; and reordering resolution to prefer Keychain over Keeper.

Verification

799 passed, 59 files. Typecheck clean apart from the pre-existing gitignored config.json error.

Summary by CodeRabbit

  • Bug Fixes

    • Improved wallet sign-in to correctly identify and remember the extension that actually completes the signing request, including fallback wallets.
    • Updated cancellation and error messages to name the extension that attempted to sign.
    • Preserved correct handling for wallet aliases, unavailable extensions, and cancelled signing requests.
  • Tests

    • Added coverage for fallback signing, extension identification, cancellation messaging, and hosting-token saves.

Closes #1361's residual case, the one #1360 and #1362 could not reach because it
is not a hardcoded string.

signBufferWithExtension takes a preferred wallet id, and when that extension has
been uninstalled mid-session it falls through to Keeper-first detection rather
than failing. The caller knew only the id it passed in, so a message about the
prompt the user had just seen named a wallet they no longer had installed.

It now returns the id it actually used. The chosen fix is the additive one the
issue named: nothing about which wallet is asked to sign changes, only what the
caller is told afterwards.

Resolution reports its own id rather than mapping the instance back to one.
Keeper aliases itself onto window.hive_keychain, so comparing object identity
against getKeychainInstance() would label Keeper "Keychain" on a Keeper-only
browser, which is the confusion this line of work exists to remove.

Login records the wallet that signed instead of the one it asked for, so a stale
preference is not written in the first place. That is where the mismatch starts.

Tests drive the real function against a stubbed window and a real localStorage,
rather than the preference path silently short-circuiting through its own
try/catch and passing for the wrong reason.

The call site is asserted separately: every behaviour test here passed with
getHostingToken reverted to the stale user.extension, since the pieces being
right is not the property, the caller using them is.
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@feruzm, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 43 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 2e7bf3e1-3224-473b-b5ff-84c8e239e98f

📥 Commits

Reviewing files that changed from the base of the PR and between 11c811a and ef6c086.

📒 Files selected for processing (2)
  • apps/self-hosted/src/features/auth/utils/extension-labels.test.ts
  • apps/self-hosted/src/features/auth/utils/hive-extensions.ts
📝 Walkthrough

Walkthrough

The authentication flow now tracks the extension that actually signs a challenge. Signing returns this extension, and login persistence and cancellation messages use it. Tests cover preferred signing, fallback wallets, aliases, missing extensions, and cancellation labels.

Changes

Signer identity tracking

Layer / File(s) Summary
Signing resolution and metadata
apps/self-hosted/src/features/auth/utils/hive-extensions.ts, apps/self-hosted/src/features/auth/utils/extension-labels.test.ts
The resolver identifies the selected signer and applies fallback rules. signBufferWithExtension returns signed.extension with the signing response. Tests cover preferred wallets, fallback signing, aliases, missing extensions, and cancellation labels.
Authentication persistence and cancellation
apps/self-hosted/src/features/auth/auth-actions.ts, apps/self-hosted/src/features/auth/utils/hosting-token.ts, apps/self-hosted/src/features/auth/utils/extension-labels.test.ts
Keychain login saves the actual signer extension as the user extension and preferred extension. Hosting-token cancellation messages use the returned signer extension.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant AuthActions
  participant signBufferWithExtension
  participant resolveSigningExtension
  participant WalletExtension
  AuthActions->>signBufferWithExtension: sign challenge
  signBufferWithExtension->>resolveSigningExtension: resolve preferred or fallback wallet
  resolveSigningExtension->>WalletExtension: request signature
  WalletExtension-->>signBufferWithExtension: return signature
  signBufferWithExtension-->>AuthActions: return signature and signed.extension
  AuthActions->>AuthActions: save signed.extension
Loading

Possibly related issues

  • Issue 1361 in ecency/vision-next: The changes track the extension that signs after fallback and use it for cancellation messages and authentication persistence.

Poem

A bunny checks the signing trail,
The true wallet now leaves its tale.
Fallback hops, identities align,
Cancellation names the signer fine.
Auth records the path it found—
Clean tracks across the token ground.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: reporting which wallet actually signed.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/self-hosted-report-signing-wallet

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
apps/self-hosted/src/features/auth/utils/extension-labels.test.ts (1)

395-420: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Replace the source assertion with a behavioral getHostingToken test.

This test parses hosting-token.ts and requires one exact argument expression. A valid refactor can break the test without changing behavior.

Mock the challenge request and signing result. Assert that getHostingToken rejects with a cancellation message for signed.extension.

As per coding guidelines: “test user-visible behavior rather than implementation details.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/self-hosted/src/features/auth/utils/extension-labels.test.ts` around
lines 395 - 420, Replace the AST/source inspection test in the “passes
signed.extension to the cancellation message” case with a behavioral test of
getHostingToken. Mock the challenge request and signing result so the signing
result contains signed.extension, then assert that getHostingToken rejects with
the corresponding cancellation message. Remove the exact
extensionCancelledMessage argument assertion and preserve coverage of the
user-visible rejection behavior.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@apps/self-hosted/src/features/auth/utils/extension-labels.test.ts`:
- Around line 395-420: Replace the AST/source inspection test in the “passes
signed.extension to the cancellation message” case with a behavioral test of
getHostingToken. Mock the challenge request and signing result so the signing
result contains signed.extension, then assert that getHostingToken rejects with
the corresponding cancellation message. Remove the exact
extensionCancelledMessage argument assertion and preserve coverage of the
user-visible rejection behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: f859df67-b1e4-48cc-b246-6606b9bbfcac

📥 Commits

Reviewing files that changed from the base of the PR and between 3b298df and 11c811a.

📒 Files selected for processing (4)
  • apps/self-hosted/src/features/auth/auth-actions.ts
  • apps/self-hosted/src/features/auth/utils/extension-labels.test.ts
  • apps/self-hosted/src/features/auth/utils/hive-extensions.ts
  • apps/self-hosted/src/features/auth/utils/hosting-token.ts

… the tree

Review point. resolveKeychainLikeDetected reproduced resolveKeychainLikeInstance's
whole decision tree, the three preference branches and the Keeper-first
fallback, and the old function stays live in sign-payment.ts and
broadcastWithExtension. Two hand-kept copies of one resolution order is the
lockstep-drift class this series keeps naming, and a divergence would send those
callers to a different wallet than the signing path names.

The instance-only function now returns the delegate's instance. Semantics are
identical, including peakvault resolving to null, which the reviewer checked and
so did I.

The test that came with this was replaced. Comparing the two functions is
comparing one with itself once delegation is in place, which asserts nothing.
Delegation makes divergence impossible by construction, so what can actually
regress is someone re-inlining the tree, and that is what is pinned: the body
must call the delegate and must not name the branches. Confirmed by re-inlining
it and watching only that case fail.
@feruzm
feruzm merged commit 35d1d55 into develop Aug 5, 2026
12 checks passed
@feruzm
feruzm deleted the feature/self-hosted-report-signing-wallet branch August 5, 2026 11:59
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.

1 participant